diff --git a/.travis.yml b/.travis.yml index 4944b5857..616351860 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,11 @@ cache: pip language: python python: - - "3.6" - "3.7" - "3.8" + - "3.9" + - "3.10" + - "3.11" script: - make test diff --git a/.vscode/launch.json b/.vscode/launch.json index 14ab7f41c..d813ccdc7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,21 +1,24 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { "name": "Python: py4web", "type": "python", "request": "launch", "program": "py4web.py", "args": [ - "run", + "run", "--errorlog=:stdout", "-L", "20", "apps" ], "console": "integratedTerminal", - "justMyCode": true, + "justMyCode": true + }, + { + "name": "Python: File", + "type": "python", + "request": "launch", + "program": "${file}", + "justMyCode": true } ] -} \ No newline at end of file +} diff --git a/Makefile b/Makefile index d54dd8e42..753b97a86 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: clean docs clean-assets assets test setup run build deploy +.PHONY: clean docs clean-assets assets tests setup run build deploy asset-apps := _dashboard _default _scaffold _minimal _documentation showcase asset-zips := $(asset-apps:%=py4web/assets/py4web.app.%.zip) clean: @@ -6,7 +6,7 @@ clean: find . -name '*~' -delete find . -name '#*' -delete rm -rf dist/* -clean-assets: +clean-assets: clean rm -f py4web/assets/* mkdir -p py4web/assets assets: clean-assets $(asset-zips) @@ -14,29 +14,27 @@ py4web/assets/py4web.app.%.zip: apps/% cd $< && find . | \ egrep "\.(py|html|css|js|png|jpg|gif|json|yaml|md|txt|mm|ico)$$" | \ zip -@ $(addprefix ../../, $@) -venv: - python3 -m venv venv - venv/bin/pip install -U pip - venv/bin/pip install ./ -docs: venv - venv/bin/pip install -U -r docs/requirements.txt - cd docs; . ../venv/bin/activate && ./updateDocs.sh html -test: venv - venv/bin/pip install -U -r test-requirements.txt - venv/bin/python -m pytest --cov=py4web --cov-report html:cov.html -v -s tests/ +docs: + pip install -U -r docs/requirements.txt + cd docs; ./updateDocs.sh html +tests: + pip install -U -r test-requirements.txt + python -m pytest --cov=py4web --cov-report html:cov.html -v tests/ setup: - venv/bin/python py4web.py setup apps - venv/bin/python py4web.py set_password + python py4web.py setup apps + python py4web.py set_password run: - venv/bin/python py4web.py run -p password.txt apps + python py4web.py run -p password.txt apps -L20 upgrade-utils: find apps -name "utils.js" -exec cp apps/_dashboard/static/js/utils.js {} \; upgrade-vue: curl -L https://unpkg.com/vue/dist/vue.min.js > apps/_dashboard/static/js/vue.min.js find apps -name "vue.min.js" -exec cp apps/_dashboard/static/js/vue.min.js {} \; build: clean assets - python3 -m pip install --upgrade build - python3 -m pip install --upgrade twine - python3 -m build + pip install --upgrade build + pip install --upgrade twine + python -m build deploy: build - python3 -m twine upload dist/* + python -m twine upload dist/* +install: + python -m pip install . diff --git a/README.rst b/README.rst index cea7c9c85..eb6d2c214 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,6 @@ What is py4web? =============== -.. image:: https://travis-ci.com/web2py/py4web.svg?branch=master - :target: https://travis-ci.com/web2py/py4web - .. image:: https://img.shields.io/pypi/v/py4web.svg :target: https://pypi.org/project/py4web/ @@ -22,32 +19,32 @@ Screenshots Running py4web -.. image:: docs/images/first_run.png +.. image:: https://py4web.com/_documentation/static/en/_images/first_run.png The main Dashboard -.. image:: docs/images/dashboard_main.png +.. image:: https://py4web.com/_documentation/static/en/_images/dashboard_main.png Editing a file in the Dashboard -.. image:: docs/images/dashboard_edit.png +.. image:: https://py4web.com/_documentation/static/en/_images/dashboard_edit.png Editing a database in the Dashboard -.. image:: docs/images/dashboard_restapi.png +.. image:: https://py4web.com/_documentation/static/en/_images/dashboard_restapi.png Installation ############ -PY4WEB runs fine on Windows, MacOS and Linux. There are many installation procedures (see the official documentation for details) but only two of them are summarized here. +PY4WEB runs fine on Windows, MacOS and Linux. There are many installation procedures `(see the official documentation for details) `__ but only two of them are summarized here. The **simplest way** to install py4web is using binaries, but it's only available for Windows and MacOS. It's meant especially for newbies or students, because it does not require Python pre-installed on your system nor administrative rights. You just need to download the latest Windows or MacOS ZIP file from `this external repository `__. Unzip it on a local folder and open a command line there. Finally run the commands (omit './' if you're using Windows) .. code:: bash - ./py4web-start set_password - ./py4web-start run apps + ./py4web set_password + ./py4web run apps @@ -78,35 +75,42 @@ Launch Arguments # py4web run -h - Usage: py4web.py run [OPTIONS] [APPS_FOLDER] + Usage: py4web.py run [OPTIONS] APPS_FOLDER Run all the applications on apps_folder Options: - -Y, --yes No prompt, assume yes to questions [default: - False] - -H, --host TEXT Host name [default: 127.0.0.1] - -P, --port INTEGER Port number [default: 8000] - -p, --password_file TEXT File for the encrypted password [default: - password.txt] - -s, --server [default|wsgiref|tornado|gunicorn|gevent|waitress| - geventWebSocketServer|wsgirefThreadingServer|rocketServer] - server to use [default: default] - -w, --number_workers INTEGER Number of workers [default: 0] - -d, --dashboard_mode TEXT Dashboard mode: demo, readonly, full, - none [default: full] - --watch [off|sync|lazy] Watch python changes and reload apps - automatically, modes: off, sync, lazy - [default: lazy] - --ssl_cert PATH SSL certificate file for HTTPS - --ssl_key PATH SSL key file for HTTPS - --errorlog TEXT Where to send error logs - (:stdout|:stderr|tickets_only|{filename}) - [default: :stderr] - -L, --logging_level INTEGER The log level (0 - 50) [default: 30 - (=WARNING)] - -D, --debug Debug switch [default: False] - -help, -h, --help Show this message and exit. + -Y, --yes No prompt, assume yes to questions + -H, --host TEXT Host listening IP [default: 127.0.0.1] + -P, --port INTEGER Port number [default: 8000] + -A, --app_names TEXT List of apps to run, comma separated (all if + omitted or empty) + -p, --password_file TEXT File for the encrypted password [default: + password.txt] + -Q, --quiet Suppress server output + -R, --routes Write apps routes to file + -s, --server [default|wsgiref|tornado|gunicorn|gevent|waitress|gunicorn|gunicornGevent| + gevent|geventWebSocketServer|geventWs| + wsgirefThreadingServer|wsgiTh|rocketServer] + Web server to use + -w, --number_workers INTEGER Number of workers [default: 0] + -d, --dashboard_mode TEXT Dashboard mode: demo, readonly, full, none + [default: full] + --watch [off|sync|lazy] Watch python changes and reload apps + automatically, modes: off, sync, lazy + [default: lazy] + --ssl_cert PATH SSL certificate file for HTTPS + --ssl_key PATH SSL key file for HTTPS + --errorlog TEXT Where to send error logs + (:stdout|:stderr|tickets_only|{filename}) + [default: :stderr] + -L, --logging_level INTEGER The log level (0 - 50) [default: 30 + (=WARNING)] + -D, --debug Debug switch + -U, --url_prefix TEXT Prefix to add to all URLs in and out + -m, --mode TEXT default or development [default: default] + -h, -help, --help Show this message and exit. + @@ -180,4 +184,4 @@ Many thanks to everyone who has contributed to the project, and especially: - `sugizo `__ - `valq7711 `__ - `Kevin Keller `__ -- `Sam de Alfaro `__ (logo design) +- Sam de Alfaro sam@dealfaro.com (logo design) diff --git a/apps/_dashboard/__init__.py b/apps/_dashboard/__init__.py index 47168e057..fcab85dce 100644 --- a/apps/_dashboard/__init__.py +++ b/apps/_dashboard/__init__.py @@ -194,7 +194,7 @@ def apps(): apps.sort(key=lambda item: item["name"]) return {"payload": apps, "status": "success"} - @action("delete_app/", method="POST") + @action("delete_app/", method="POST") @session_secured def delete_app(name): """delete the app""" @@ -209,7 +209,7 @@ def delete_app(name): return {"status": "success", "payload": "Deleted"} return {"status": "success", "payload": "App does not exist"} - @action("new_file//", method="POST") + @action("new_file//", method="POST") @session_secured def new_file(name, file_name): """creates a new file""" @@ -417,9 +417,9 @@ def save(path, reload_app=True): """Saves a file""" app_name = path.split("/")[0] path = safe_join(FOLDER, path) or abort() - with open(path, "w") as myfile: + with open(path, "wb") as myfile: body = json.load(request.body) - myfile.write(body) + myfile.write(body.encode("utf8")) if reload_app: Reloader.import_app(app_name) return {"status": "success"} @@ -567,7 +567,10 @@ def gitshow(project, commit): @action.uses(Logged(session), "translations.html") def translations(name): """returns a json with all translations for all languages""" - t = Translator(os.path.join(FOLDER, name, "translations")) + folder = os.path.join(FOLDER, name, "translations") + if not os.path.exists(folder): + os.makedirs(folder) + t = Translator(folder) return t.languages diff --git a/apps/_dashboard/static/components/mtable.js b/apps/_dashboard/static/components/mtable.js index 413c03a87..7a42bb508 100644 --- a/apps/_dashboard/static/components/mtable.js +++ b/apps/_dashboard/static/components/mtable.js @@ -52,10 +52,10 @@ if (filters.length) url += '&'+filters.join('&'); if (self.order) url += '&@order='+self.order; self.busy = true; - axios.get(url).then(function (res) { + Q.get(url).then(function (res) { self.busy = false; - if(!length) self.table = res.data; - else self.table.items = self.table.items.concat(res.data.items); + if(!length) self.table = res.json(); + else self.table.items = self.table.items.concat(res.json().items); }); }; @@ -100,8 +100,8 @@ reference_table_url.pop() reference_table_url.push(field.references) reference_table_url = reference_table_url.join('/') + '?@options_list=true'; - axios.get(reference_table_url).then(function (res) { - let url_components = res.config.url.split('?')[0].split('/'); + Q.get(reference_table_url).then(function (res) { + let url_components = res.json().config.url.split('?')[0].split('/'); self.reference_options[url_components[url_components.length - 1 ]] = res.data.items; }); @@ -129,7 +129,7 @@ if (window.confirm("Really delete record?")) { let url = this.url + '/' + item.id; this.table.items = this.table.items.filter((i)=>{return i.id != item.id;}); - axios.delete(url); + Q.delete(url); if (item==this.item) this.item = null; } }; @@ -150,27 +150,30 @@ } if (item.id) { url += '/' + item.id; - axios.put(url, item).then(mtable.handle_response('put', this), - mtable.handle_response('put', this)); + var data = JSON.parse(JSON.stringify(item)); + delete data["id"]; + Q.put(url, data).then(mtable.handle_response('put', this), + mtable.handle_response('put', this)); } else { - axios.post(url, item).then(mtable.handle_response('post', this), - mtable.handle_response('post', this)); - } + Q.post(url, item).then(mtable.handle_response('post', this), + mtable.handle_response('post', this)); + } }; mtable.handle_response = function(method, data) { self.busy = false; return function(res) { - if (res.response) res = res.response; // deal with error weirdness + res = res.json(); if (method == 'post') { data.table.items = []; mtable.methods.load.call(data); } - if (res.data.status == 'success') { + if (res.status == 'success') { data.clear(); + location.reload(); } else { - data.errors = res.data.errors; - data.message = res.data.message; + data.errors = res.errors; + data.message = res.message; } }; }; diff --git a/apps/_dashboard/static/js/axios.min.js b/apps/_dashboard/static/js/axios.min.js deleted file mode 100644 index 2d030546a..000000000 --- a/apps/_dashboard/static/js/axios.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* axios v0.20.0 | (c) 2020 by Matt Zabriskie */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(4),a=n(22),u=n(10),c=r(u);c.Axios=s,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function s(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){u.headers[e]={}}),i.forEach(["post","put","patch"],function(e){u.headers[e]=i.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),i=n(16),s=n(5),a=n(17),u=n(20),c=n(21),f=n(14);e.exports=function(e){return new Promise(function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"],(r.isBlob(p)||r.isFile(p))&&p.type&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=unescape(encodeURIComponent(e.auth.password))||"";d.Authorization="Basic "+btoa(h+":"+m)}var y=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),s(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?u(l.getAllResponseHeaders()):null,i=e.responseType&&"text"!==e.responseType?l.response:l.responseText,s={data:i,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,s),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=(e.withCredentials||c(y))&&e.xsrfCookieName?i.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),n(e),l=null)}),p||(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),s===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(18),o=n(19);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){function n(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function o(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(e[o],t[o])}t=t||{};var i={},s=["url","method","data"],a=["headers","auth","proxy","params"],u=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],c=["validateStatus"];r.forEach(s,function(e){r.isUndefined(t[e])||(i[e]=n(void 0,t[e]))}),r.forEach(a,o),r.forEach(u,function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(void 0,t[o])}),r.forEach(c,function(r){r in t?i[r]=n(e[r],t[r]):r in e&&(i[r]=n(void 0,e[r]))});var f=s.concat(a).concat(u).concat(c),p=Object.keys(e).concat(Object.keys(t)).filter(function(e){return f.indexOf(e)===-1});return r.forEach(p,o),i}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); -//# sourceMappingURL=axios.min.map \ No newline at end of file diff --git a/apps/_dashboard/static/js/index.js b/apps/_dashboard/static/js/index.js index 26072e340..9b0d1b515 100644 --- a/apps/_dashboard/static/js/index.js +++ b/apps/_dashboard/static/js/index.js @@ -9,16 +9,6 @@ Vue.component('treefiles', { } }); -let post= function(url, data) { - return fetch(url, { - method: "POST", - cache: "no-cache", - headers: { "Content-Type": "application/json" }, - redirect: "follow", - body: JSON.stringify(data) - }); -}; - let app = {}; let init = (app) => { app.data = { @@ -43,9 +33,7 @@ let init = (app) => { }; app.select_app = (appobj) => { app.vue.selected_app = appobj; - app.vue.walk = []; - fetch('../walk/'+appobj.name).then(r=>r.json()).then(r=>{app.vue.walk=r.payload;}); - fetch('../rest/'+appobj.name).then(r=>r.json()).then(r=>{app.vue.databases=r.databases;}); + app.reload_files(); }; app.activate_editor = (path, payload) => { app.vue.files[path] = payload; @@ -80,8 +68,8 @@ let init = (app) => { } else { var url = '../load/'+path; if(app.vue.selected_type != 'text') url = '../load_bytes/'+path; - fetch(url).then(r=>r.json()).then(r=>{ - app.activate_editor(path, r.payload); + Q.get(url).then(r=>{ + app.activate_editor(path, r.json().payload); }); } } @@ -93,7 +81,7 @@ let init = (app) => { app.reload = (name) => { app.modal_dismiss(); app.vue.loading = true; - fetch(name?'../reload/'+name:'../reload').then(r=>r.json()).then(r=>app.init()); + Q.get(name?'../reload/'+name:'../reload').then(r=>app.init()); }; app.gitlog = (name) => { window.open('../gitlog/'+name); @@ -106,9 +94,13 @@ let init = (app) => { // pass }; app.save_file = () => { + if(app.vue.selected_type != 'text') { + alert("Unable to save this file, it is not of type text"); + return; + } var path = app.vue.selected_filename; app.vue.files[path] = app.editor.getValue(); - post('../save/'+path, app.vue.files[path]).then(r=>app.file_saved()); + Q.post('../save/'+path, app.vue.files[path]).then(r=>app.file_saved()); }; app.download_selected_app = () => { var url = '../packed/py4web.app.' + app.vue.selected_app.name + '.zip?' + (new Date()).getTime() @@ -129,7 +121,7 @@ let init = (app) => { else if(form.mode=='new' && app.vue.apps.map((a)=>{return a.name;}).indexOf(form.name)>=0) { alert('Cannot create an app with this name. It already exists'); } else { - post('../new_app', form).then(r=>app.reload()); + Q.post('../new_app', form).then(r=>app.reload()); } }; app.process_new_file = () => { @@ -137,9 +129,9 @@ let init = (app) => { var form = app.vue.modal.form; if(!form.filename) { alert('An file name must be provided'); return; } /*reload entire page needed to see the new file listed*/ - post('../new_file/'+app_name+'/'+form.filename).then(r=>{ + Q.post('../new_file/'+app_name+'/'+form.filename).then(r=>{ app.vue.walk = []; - fetch('../walk/'+app_name).then(r=>r.json()).then(r=>{app.vue.walk=r.payload;}); + Q.get('../walk/'+app_name).then(r=>{app.vue.walk=r.json().payload;}); app.modal_dismiss(); }); }; @@ -176,54 +168,63 @@ let init = (app) => { var name = app.vue.selected_filename; app.confirm("Delete File","blue","Do you really want to delete "+name+"?",()=>{ app.modal_dismiss(); - post('../delete/'+name).then(r=>app.init()); + Q.post('../delete/'+name).then(r=>app.init()); }); }; app.delete_selected_app = () => { var name = app.vue.selected_app.name; app.confirm("Delete App","blue","Do you really want to delete "+name+"?",()=>{ app.modal_dismiss(); - post('../delete_app/'+name).then(r=>app.init()); + Q.post('../delete_app/'+name).then(r=>app.init()); }); }; app.reload_info = () => { - fetch('../info').then(r=>r.json()).then(r=>{ - app.vue.info=r.payload || []; + Q.get('../info').then(r=>{ + app.vue.info=r.json().payload || []; }); }; app.reload_apps = () => { - fetch('../apps').then(r=>r.json()).then(r=>{ - app.vue.apps=r.payload || []; app.update_selected(); + Q.get('../apps').then(r=>{ + app.vue.apps=r.json().payload || []; app.update_selected(); }); }; app.reload_routes = () => { - fetch('../routes').then(r=>r.json()).then(r=>{ - app.vue.routes=r.payload || []; + Q.get('../routes').then(r=>{ + app.vue.routes=r.json().payload || []; }); }; app.reload_tickets = () => { app.vue.tickets = []; - fetch('../tickets').then(r=>r.json()).then(r=>{ - app.vue.tickets = r.payload || []; + Q.get('../tickets').then(r=>{ + app.vue.tickets = r.json().payload || []; }); }; + app.reload_files = () => { + if (!app.vue.selected_app) return; + app.vue.walk = []; + var name = app.vue.selected_app.name; + Q.get('../walk/'+name).then(r=>{app.vue.walk=r.json().payload;}); + Q.get('../rest/'+name).then(r=>{app.vue.databases=r.json().databases;}); + app.vue.selected_filename = null; + } app.clear_tickets = () => { app.vue.tickets = []; - fetch('../clear').then(r=>app.reload_tickets()); + Q.get('../clear').then(r=>app.reload_tickets()); }; app.login = () => { - post('../login', {'password': app.vue.password}) - .then(r=>r.json()) + Q.post('../login', {'password': app.vue.password}) + .then(r=>{ app.vue.password = ''; if( r.user) { app.vue.user = true; app.init(); } + location.reload(); }); }; app.logout = () => { - post('../logout').then(r=>window.location.reload()); + Q.post('../logout').then(r=>window.location.reload()); }; app.methods = { select: app.select_app, @@ -258,11 +259,12 @@ let init = (app) => { return a.name==app.vue.selected_app.name; })[0]; }; - app.init = () => { + app.init = () => { app.reload_info(); app.reload_apps(); app.reload_routes(); app.reload_tickets(); + app.reload_files(); setTimeout(()=>{app.vue.loading=false;}, 1000); }; if (USER_ID) app.init(); diff --git a/apps/_dashboard/static/js/translations.js b/apps/_dashboard/static/js/translations.js index fcd134272..50f743443 100644 --- a/apps/_dashboard/static/js/translations.js +++ b/apps/_dashboard/static/js/translations.js @@ -33,15 +33,15 @@ var init_app = function() { }; self.methods.save_languages = function() { let data = self.vue.translations; - axios.post('/' + self.base + '/api/translations/' + self.app, data).then( + Q.post('/' + self.base + '/api/translations/' + self.app, data).then( function(res) { alert("Saved"); }, function(res) { alert("Error Saving"); } ); }; self.methods.update_languages = function() { - axios.get('/' + self.base + '/api/translations/' + self.app + '/search').then( + Q.get('/' + self.base + '/api/translations/' + self.app + '/search').then( function(res) { - let words = res.data.strings; + let words = res.json().strings; for (var lang in self.vue.translations) { var translations = self.vue.translations[lang]; words.map(function(key) { @@ -78,8 +78,8 @@ var init_app = function() { self.methods.select_language(language); }; self.vue = new Vue({ el: '#vue', data: self.data, methods: self.methods }); - axios.get('/' + self.base + '/api/translations/' + self.app).then( - function(res) { self.vue.translations = res.data; } + Q.get('/' + self.base + '/api/translations/' + self.app).then( + function(res) { self.vue.translations = res.json(); } ); return self; } diff --git a/apps/_dashboard/static/js/utils.js b/apps/_dashboard/static/js/utils.js index c0ba784d2..aa2bd424c 100644 --- a/apps/_dashboard/static/js/utils.js +++ b/apps/_dashboard/static/js/utils.js @@ -43,12 +43,18 @@ Q.ajax = function(method, url, data, headers) { return new Promise(function(resolve, reject) { fetch(url, options).then(function(res){ res.text().then(function(body){ - res.data = body; + res.data = body; res.json = function(){return JSON.parse(body);}; resolve(res); }, reject);}).catch(reject); }); } + +Q.get = (url, headers) => Q.ajax("GET", url, null, headers); +Q.post = (url, data, headers) => Q.ajax("POST", url, data, headers); +Q.put = (url, data, headers) => Q.ajax("PUT", url, data, headers); +Q.delete = (url, headers) => Q.ajax("DELETE", url, null, headers); + // Gets a cookie value Q.get_cookie = function (name) { var cookie = RegExp("" + name + "[^;]+").exec(document.cookie); diff --git a/apps/_dashboard/templates/dbadmin.html b/apps/_dashboard/templates/dbadmin.html index 7ab18ec7f..e501ec664 100644 --- a/apps/_dashboard/templates/dbadmin.html +++ b/apps/_dashboard/templates/dbadmin.html @@ -21,7 +21,6 @@ - diff --git a/apps/_dashboard/templates/gitlog.html b/apps/_dashboard/templates/gitlog.html index 9306b2ef5..a2ee5c504 100644 --- a/apps/_dashboard/templates/gitlog.html +++ b/apps/_dashboard/templates/gitlog.html @@ -83,8 +83,6 @@
  • m (>>)
  • - - - diff --git a/apps/_default/__init__.py b/apps/_default/__init__.py index b5d43cbb0..a6344d6aa 100644 --- a/apps/_default/__init__.py +++ b/apps/_default/__init__.py @@ -1,7 +1,10 @@ -from py4web import action, __version__ - +import os +from py4web import action, Cache +cache = Cache(size=1000) @action("index") -@action.uses("index.html") +@cache.memoize(expiration=1) def index(): - return dict(version=__version__) + filename = os.path.join(os.path.dirname(__file__), "static", "index.html") + with open(filename) as stream: + return stream.read() diff --git a/apps/_default/static/css/prism.css b/apps/_default/static/css/prism.css new file mode 100644 index 000000000..221f20cfd --- /dev/null +++ b/apps/_default/static/css/prism.css @@ -0,0 +1,3 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+python&plugins=remove-initial-line-feed+normalize-whitespace */ +code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} diff --git a/apps/_default/static/index.html b/apps/_default/static/index.html new file mode 100644 index 000000000..b3df0406e --- /dev/null +++ b/apps/_default/static/index.html @@ -0,0 +1,348 @@ + + + + + + + + + Fork me on GitHub +
    + +

    PY4WEB

    +

    Different, yet cute, and a memorable evolutionary step.

    + Dashboard + Documentation + Examples + Tutorials + Source + Discuss +
    +

    +
    +
    +
    +

    WHAT IS PY4WEB?

    +
    + py4web is a framework for rapid development of secure database driven web applications. + It is the successor of web2py but much improved. + +
    + Install it and start it + +
    
    +$ pip install py4web               # install it (but use a venv or Nix)
    +$ py4web setup apps                # answer yes to all questions
    +$ py4web set_password              # pick a password for the admin
    +$ cp -r apps/_scaffold apps/myapp  # make a new app
    +$ py4web run apps                  # start py4web
    +
    + +Each subfolder of apps/ with an __init__.py is its own app. One py4web can run multiple apps. +You just copy the _scaffold app to make a new one. + +
    + + The basic functions/objects are imported from the py4web module. + +
    
    +from py4web import action, redirect, request, URL, Field
    +
    + +
    + Use @action to map URLs into functions (aka actions). Actions can return strings or dictionaries. + +
    
    +# http://127.0.0.1:8000/myapp/index
    +@action("index")
    +def index():
    +    return "hello world"
    +
    + +
    + Actions can map path_info items into variables + +
    
    +# http://127.0.0.1:8000/myapp/index/1
    +@action("index/<x:int>")
    +def index(x):
    +    return f"x = {x}"
    +
    + +
    + py4web uses a request object from ombott, compatible with + bottlepy + +
    
    +# http://127.0.0.1:8000/myapp/index/?x=1
    +@action("index")
    +def index():
    +    x = request.query.get("x")
    +    return f"x = {x}"
    +
    + +
    + It can parse JSON from POST requests for example + +
    
    +# http://127.0.0.1:8000/myapp/index POST {x: 1}
    +@action("index", method="POST")
    +def index():
    +    x = request.json.get("x")
    +    return {"x": x}
    +
    + +
    + A page can redirect to another page + +
    
    +@action("index")
    +def index():
    +    redirect("http://example.com")
    +
    + +
    + We use URL to generate the urls of internal pages + +
    
    +@action("index")
    +def index():
    +    redirect(URL("other_page"))
    +
    + +
    + We have a built-in session object which by default stores the session data, signed, in a cookie. Optionally it can be stored in db, redis, + or other custom storage. Session is a fixture + and it must be declared with @action.uses. + Think of fixtures as per action (as opposed to per app) middleware. + +
    
    +@action("index")
    +@action.uses(session)
    +def index():
    +    session.x = (session.x or 0) + 1 
    +    return f"x = {x}"
    +
    + +
    + An action can return a dictionary and use a template to render the dictionary into HTML. A template is also a fixture and it must be declared with @action.uses. + +
    
    +@action("index")
    +@action.uses("index.html")
    +def index():
    +    x = 1
    +    return locals()
    +
    + +
    + A template can be any text but typically it is HTML. Templates can extend and include other templates. Templetes can embed variables with [[=x]] and they can also embed python code (without limitations) with double square brakets. Indentation does not matter. [[pass]] closes [[ if ... ]] and [[ for ... ]]. + +
    
    +[[extend "layout.html"]]
    +

    x = [[=x]]

    + +[[ for i in range(10): ]][[ if i % 2==0: ]] +[[=i]] is even +[[ pass ]][[ pass ]] +
    + +
    + Py4web comes with a built-in auth object that generates all the pages + required for user registration, login, email verification, retrieve and change password, edit profile, single sign on with OAuth2 and more. + auth is also a fixture which exposed the current user to the action. Notice that fixtures have dependencies, and by including + auth its dependencies (db, session, flash) are also included automatically. + +
    
    +@action("index")
    +@action.uses("generic.html", auth)
    +def index():
    +    user = auth.get_user()
    +    if user:
    +        message = f"Hello {user['first_name']}"
    +    else:
    +        message = "Hello, you are not logged in"
    +    return {"message": message}
    +
    + +
    + auth.user is a different fixture which requires a logged-in user and blocks access otherwise + +
    
    +@action("index")
    +@action.uses("generic.html", auth.user)
    +def index():
    +    user = auth.get_user()
    +    message = f"Hello {user['first_name']}"
    +    return {"message": message}
    +
    + +
    + More complex policies are possible using the built-in tagging + system combined with auth. + Condition is another fixture, if False it raises a 404 error page by default. + +
    
    +is_manager = Condition(lambda: "manager" in groups.get(auth.user_id))
    +
    +@action("index")
    +@action.uses("generic.html", auth.user, is_manager)
    +def index():
    +    user = auth.get_user()
    +    message = f"Hello {user['first_name']} (manager!)"
    +    return {"message": message}
    +
    + +
    + Py4web has a built-in Database Abstraction Layer (support for sqlite, postgres, mysql, oracle, and more). + It is integrated with auth and with form generation logic. It follows a declarative pattern and + provides automatic migrations to create/alter tables. For example the following code creates a "thing" table with a "name" field and and an "image" and an additional standard signature fields ("created_by", "created_on", "modified_by", "modified_on"). Field types are more complex than basic database types as they have logic for validation and for handling content (such as uploading and downloading images). + +
    
    +db.define_table(
    +    "thing",
    +    Field("name", requires=IS_NOT_EMPTY()),
    +    Field("image", "upload", download_url = lambda fn: URL(f"download/{fn}")),
    +    auth.signature)
    +
    + +
    + Given the object db.thing defined above py4web can automatically generate forms including validation. +Here is a create form + +
    
    +@action("create_thing")
    +@action.uses("generic.html", auth.user)
    +def create_thing():
    +    form = Form(db.thing)
    +    if form.accepted:
    +        # record created
    +        redirect(URL("index"))
    +    return locals()
    +
    + +
    + Here is an edit form + +
    
    +@action("edit_thing/<thing_id:int>")
    +@action.uses("generic.html", auth.user)
    +def edit_thing(thing_id):
    +    form = Form(db.thing, thing_id)
    +    if form.accepted:
    +        # record updated
    +        redirect(URL("index"))
    +    return locals()
    +
    + +
    + py4web can also generate a grid from a database query. + The grid shows selected records with pagination and, optionally, enables creating, editing, deleting records, with multiple options for customization + +
    
    +@action("my_things")
    +@action("my_things/<path:path>")
    +@action.uses("generic.html", auth.user)
    +def my_things(path=None):
    +    form = Grid(path,
    +                db.thing.created_by==auth.user_id,
    +                editable=True, create=True, deletable=True)
    +    return locals()
    +
    + +
    + The DAL also makes it very easy to create APIs. Here is a GET API example + +
    
    +@action("api/things", method="GET")
    +@action.uses(db)
    +def api_GET_things():
    +    return {"things": db(db.thing).select().as_list()}
    +
    + +
    + POST API example + +
    
    +@action("api/things", method="POST")
    +@action.uses(db)
    +def api_POST_things():
    +    return db.thing.validate_and_insert(**request.json)
    +
    + +
    + PUT API example + +
    
    +@action("api/things/<thing_id:int>", method="PUT")
    +@action.uses(db)
    +def api_PUT_things(thing_id):
    +    return db.thing.validate_and_update(thing_id, **request.json)
    +
    + +
    + DELETE API example + +
    
    +@action("api/things/<thing_id:int>", method="DELETE")
    +@action.uses(db)
    +def api_DELETE_things(thing_id):
    +    return {"deleted": db(db.thing.id==thing_id).delete()}
    +
    +
    + + + +
    + These are just the basics. There is a lot more to it, including... + +
    + +

    LICENSE

    + + 3-clause BSD + +

    USEFUL LINKS

    + + + +
    + + +
    +
    + + + diff --git a/apps/_default/static/js/prism.js b/apps/_default/static/js/prism.js new file mode 100644 index 000000000..4cfc5759f --- /dev/null +++ b/apps/_default/static/js/prism.js @@ -0,0 +1,10 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+python&plugins=remove-initial-line-feed+normalize-whitespace */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; +!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; +Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; +"undefined"!=typeof Prism&&"undefined"!=typeof document&&Prism.hooks.add("before-sanity-check",(function(e){if(e.code){var n=e.element.parentNode,o=/(?:^|\s)keep-initial-line-feed(?:\s|$)/;!n||"pre"!==n.nodeName.toLowerCase()||o.test(n.className)||o.test(e.element.className)||(e.code=e.code.replace(/^(?:\r?\n|\r)/,""))}})); +!function(){if("undefined"!=typeof Prism){var e=Object.assign||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},t={"remove-trailing":"boolean","remove-indent":"boolean","left-trim":"boolean","right-trim":"boolean","break-lines":"number",indent:"number","remove-initial-line-feed":"boolean","tabs-to-spaces":"number","spaces-to-tabs":"number"};n.prototype={setDefaults:function(t){this.defaults=e(this.defaults,t)},normalize:function(t,n){for(var r in n=e(this.defaults,n)){var i=r.replace(/-(\w)/g,(function(e,t){return t.toUpperCase()}));"normalize"!==r&&"setDefaults"!==i&&n[r]&&this[i]&&(t=this[i].call(this,t,n[r]))}return t},leftTrim:function(e){return e.replace(/^\s+/,"")},rightTrim:function(e){return e.replace(/\s+$/,"")},tabsToSpaces:function(e,t){return t=0|t||4,e.replace(/\t/g,new Array(++t).join(" "))},spacesToTabs:function(e,t){return t=0|t||4,e.replace(RegExp(" {"+t+"}","g"),"\t")},removeTrailing:function(e){return e.replace(/\s*?$/gm,"")},removeInitialLineFeed:function(e){return e.replace(/^(?:\r?\n|\r)/,"")},removeIndent:function(e){var t=e.match(/^[^\S\n\r]*(?=\S)/gm);return t&&t[0].length?(t.sort((function(e,t){return e.length-t.length})),t[0].length?e.replace(RegExp("^"+t[0],"gm"),""):e):e},indent:function(e,t){return e.replace(/^[^\S\n\r]*(?=\S)/gm,new Array(++t).join("\t")+"$&")},breakLines:function(e,t){t=!0===t?80:0|t||80;for(var n=e.split("\n"),i=0;it&&(o[l]="\n"+o[l],a=s)}n[i]=o.join("")}return n.join("\n")}},"undefined"!=typeof module&&module.exports&&(module.exports=n),Prism.plugins.NormalizeWhitespace=new n({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-sanity-check",(function(e){var n=Prism.plugins.NormalizeWhitespace;if((!e.settings||!1!==e.settings["whitespace-normalization"])&&Prism.util.isActive(e.element,"whitespace-normalization",!0))if(e.element&&e.element.parentNode||!e.code){var r=e.element.parentNode;if(e.code&&r&&"pre"===r.nodeName.toLowerCase()){for(var i in null==e.settings&&(e.settings={}),t)if(Object.hasOwnProperty.call(t,i)){var o=t[i];if(r.hasAttribute("data-"+i))try{var a=JSON.parse(r.getAttribute("data-"+i)||"true");typeof a===o&&(e.settings[i]=a)}catch(e){}}for(var l=r.childNodes,s="",c="",u=!1,m=0;m - - - - - - - Fork me on GitHub -
    - -

    PY4WEB

    -

    Different, yet cute, and a memorable evolutionary step.

    - Dashboard - Documentation - Examples - Tutorials - Source - Discuss -
    - - diff --git a/apps/_documentation/static/en/.buildinfo b/apps/_documentation/static/en/.buildinfo index 4ffd52044..d5364a3ce 100644 --- a/apps/_documentation/static/en/.buildinfo +++ b/apps/_documentation/static/en/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 6caf0f7db59c0515710115373bfa6456 +config: 05361d6f8693871fc2dbfcc71260e2ee tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/apps/_documentation/static/en/_images/first_run.png b/apps/_documentation/static/en/_images/first_run.png index 59320040c..a670f0150 100644 Binary files a/apps/_documentation/static/en/_images/first_run.png and b/apps/_documentation/static/en/_images/first_run.png differ diff --git a/apps/_documentation/static/en/_images/form1.png b/apps/_documentation/static/en/_images/form1.png index 6f66b7257..4839a27f9 100644 Binary files a/apps/_documentation/static/en/_images/form1.png and b/apps/_documentation/static/en/_images/form1.png differ diff --git a/apps/_documentation/static/en/_images/form2.png b/apps/_documentation/static/en/_images/form2.png deleted file mode 100644 index b83647c2f..000000000 Binary files a/apps/_documentation/static/en/_images/form2.png and /dev/null differ diff --git a/apps/_documentation/static/en/_images/form3.png b/apps/_documentation/static/en/_images/form3.png deleted file mode 100644 index 3de9bbba9..000000000 Binary files a/apps/_documentation/static/en/_images/form3.png and /dev/null differ diff --git a/apps/_documentation/static/en/_images/form4.png b/apps/_documentation/static/en/_images/form4.png deleted file mode 100644 index 9534e4683..000000000 Binary files a/apps/_documentation/static/en/_images/form4.png and /dev/null differ diff --git a/apps/_documentation/static/en/_images/form5.png b/apps/_documentation/static/en/_images/form5.png deleted file mode 100644 index 9b88b04ad..000000000 Binary files a/apps/_documentation/static/en/_images/form5.png and /dev/null differ diff --git a/apps/_documentation/static/en/_images/form6.png b/apps/_documentation/static/en/_images/form6.png deleted file mode 100644 index 9761c347e..000000000 Binary files a/apps/_documentation/static/en/_images/form6.png and /dev/null differ diff --git a/apps/_documentation/static/en/_static/_sphinx_javascript_frameworks_compat.js b/apps/_documentation/static/en/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 000000000..81415803e --- /dev/null +++ b/apps/_documentation/static/en/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/apps/_documentation/static/en/_static/basic.css b/apps/_documentation/static/en/_static/basic.css index 7577acb1a..f316efcb4 100644 --- a/apps/_documentation/static/en/_static/basic.css +++ b/apps/_documentation/static/en/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -237,6 +237,10 @@ a.headerlink { visibility: hidden; } +a:visited { + color: #551A8B; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -670,6 +674,16 @@ dd { margin-left: 30px; } +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; @@ -738,6 +752,14 @@ abbr, acronym { cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { diff --git a/apps/_documentation/static/en/_static/css/theme.css b/apps/_documentation/static/en/_static/css/theme.css index c03c88f06..19a446a0e 100644 --- a/apps/_documentation/static/en/_static/css/theme.css +++ b/apps/_documentation/static/en/_static/css/theme.css @@ -1,4 +1,4 @@ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/apps/_documentation/static/en/_static/doctools.js b/apps/_documentation/static/en/_static/doctools.js index d06a71d75..4d67807d1 100644 --- a/apps/_documentation/static/en/_static/doctools.js +++ b/apps/_documentation/static/en/_static/doctools.js @@ -4,7 +4,7 @@ * * Base JavaScript utilities for all Sphinx HTML documentation. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/apps/_documentation/static/en/_static/documentation_options.js b/apps/_documentation/static/en/_static/documentation_options.js index 593eb28e9..7d5644e0b 100644 --- a/apps/_documentation/static/en/_static/documentation_options.js +++ b/apps/_documentation/static/en/_static/documentation_options.js @@ -1,6 +1,5 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.20230507.1', +const DOCUMENTATION_OPTIONS = { + VERSION: '20240915', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/apps/_documentation/static/en/_static/jquery.js b/apps/_documentation/static/en/_static/jquery.js new file mode 100644 index 000000000..c4c6022f2 --- /dev/null +++ b/apps/_documentation/static/en/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms) => { +const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; const [docName, title, anchor, descr, score, _filename] = item; @@ -75,28 +75,35 @@ const _displayItem = (item, searchTerms) => { if (dirname.match(/\/index\/$/)) dirname = dirname.substring(0, dirname.length - 6); else if (dirname === "index/") dirname = ""; - requestUrl = docUrlRoot + dirname; + requestUrl = contentRoot + dirname; linkUrl = requestUrl; } else { // normal html builders - requestUrl = docUrlRoot + docName + docFileSuffix; + requestUrl = contentRoot + docName + docFileSuffix; linkUrl = docName + docLinkSuffix; } let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; linkEl.innerHTML = title; - if (descr) + if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) + Search.makeSearchSummary(data, searchTerms, anchor) ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); }); Search.output.appendChild(listItem); }; @@ -109,26 +116,43 @@ const _finishSearch = (resultCount) => { ); else Search.status.innerText = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( results, resultCount, - searchTerms + searchTerms, + highlightTerms, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms); + _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), 5 ); } // search finished, update title and status message else _finishSearch(resultCount); }; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; /** * Default splitQuery function. Can be overridden in ``sphinx.search`` with a @@ -152,13 +176,26 @@ const Search = { _queued_query: null, _pulse_status: -1, - htmlToText: (htmlString) => { + htmlToText: (htmlString, anchor) => { const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; + if (docContent) return docContent.textContent; + console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." ); return ""; }, @@ -231,16 +268,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -276,21 +304,38 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - // array of [docname, title, anchor, descr, score, filename] - let results = []; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + _removeChildren(document.getElementById("search-progress")); - const queryLower = query.toLowerCase(); + const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, id !== null ? "#" + id : "", null, - score, + score + boost, filenames[file], ]); } @@ -300,46 +345,47 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ docNames[file], titles[file], id ? "#" + id : "", null, score, filenames[file], - ]); + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } } } } // lookup as object objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)) ); // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); - - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; - }); + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; // remove duplicate search results // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept @@ -353,14 +399,19 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging //Search.lastresults = results.slice(); // a copy // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms); }, /** @@ -458,14 +509,18 @@ const Search = { // add support for partial matches if (word.length > 2) { const escapedWord = _escapeRegExp(word); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); - }); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } } // no match but word was a required one @@ -488,9 +543,8 @@ const Search = { // create the mapping files.forEach((file) => { - if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [word]); + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); }); }); @@ -541,8 +595,8 @@ const Search = { * search summary for a given text. keywords is a list * of stemmed words. */ - makeSearchSummary: (htmlText, keywords) => { - const text = Search.htmlToText(htmlText); + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); if (text === "") return null; const textLower = text.toLowerCase(); diff --git a/apps/_documentation/static/en/_static/sphinx_highlight.js b/apps/_documentation/static/en/_static/sphinx_highlight.js new file mode 100644 index 000000000..8a96c69a1 --- /dev/null +++ b/apps/_documentation/static/en/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/apps/_documentation/static/en/chapter-01.html b/apps/_documentation/static/en/chapter-01.html index a26806cb1..0aae81d03 100644 --- a/apps/_documentation/static/en/chapter-01.html +++ b/apps/_documentation/static/en/chapter-01.html @@ -1,25 +1,27 @@ - + - + - What is py4web? — py4web 1.20230507.1 documentation - - - - + What is py4web? — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
    - 1.20230507.1 + 20240915
    @@ -59,7 +61,7 @@
  • Help, resources and hints
  • Installation and Startup
  • The Dashboard
  • -
  • Creating your first app
  • +
  • Creating an app
  • Fixtures
  • The Database Abstraction Layer (DAL)
  • The RestAPI
  • @@ -98,7 +100,7 @@
    -

    What is py4web?

    +

    What is py4web?

    PY4WEB is a web framework for rapid development of efficient database driven web applications. It is an evolution of the popular web2py framework, but much faster and slicker. Its internal design has been much @@ -198,7 +200,7 @@

    What is py4web? -

    Acknowledgments

    +

    Acknowledgments

    Many thanks to everyone who has contributed to the project, and especially:

    Special thanks to Sam de Alfaro, who designed the official logo of py4web. We friendly call the logo “Axel the axolotl”: it magically represents the sense of kindness and inclusion. We believe it’s the cornerstone of our growing community.

    _images/logo.png @@ -231,7 +233,7 @@

    Acknowledgments -

    © Copyright 2020, BSDv3 License.

    +

    © Copyright 2024, BSDv3 License.

    Built with Sphinx using a @@ -256,7 +258,7 @@

    Acknowledgments - v: 1.20230507.1 + v: 20240915
    diff --git a/apps/_documentation/static/en/chapter-02.html b/apps/_documentation/static/en/chapter-02.html index 6b5fbc96f..88d4f3d33 100644 --- a/apps/_documentation/static/en/chapter-02.html +++ b/apps/_documentation/static/en/chapter-02.html @@ -1,25 +1,27 @@ - + - + - Help, resources and hints — py4web 1.20230507.1 documentation - - - - + Help, resources and hints — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
    - 1.20230507.1 + 20240915
    @@ -74,7 +76,7 @@
  • Installation and Startup
  • The Dashboard
  • -
  • Creating your first app
  • +
  • Creating an app
  • Fixtures
  • The Database Abstraction Layer (DAL)
  • The RestAPI
  • @@ -113,53 +115,61 @@
    -

    Help, resources and hints

    +

    Help, resources and hints

    We’ve made our best to make PY4WEB simple and clean. But you know, modern web programming is a daunting task. It requires an open mind, able to jump frequently (without being lost!) from python to HTML to javascript to css and even database management. -But don’t be scared, in this manual we’ll assist you side by side in this journey. And there are many other valuable resources that we’re going to show you.

    +But don’t be scared, in this manual we’ll assist you side by side in this journey. And there are many other valuable resources that we’re going to show +you.

    -

    Resources

    +

    Resources

    -

    This manual

    -

    This manual is the Reference Manual for py4web. It’s available online at https://py4web.com/_documentation/static/index.html, where you’ll also find the PDF and EBOOK version, in multiple languages. It written in RestructuredText and generated using Sphinx.

    +

    This manual

    +

    This manual is the Reference Manual for py4web. It’s available online at https://py4web.com/_documentation/static/index.html, where you’ll also find the +PDF and EBOOK version, in multiple languages. It written in RestructuredText and generated using Sphinx.

    -

    The Google group

    -

    There is a dedicated mailing list hosted on Google Groups, see https://groups.google.com/g/py4web. This is the main source of discussions for developers and simple users. For any problem you should face, this is the right place to search for a hint or a solution.

    +

    The Google group

    +

    There is a dedicated mailing list hosted on Google Groups, see https://groups.google.com/g/py4web. This is the main source of discussions for developers +and simple users. For any problem you should face, this is the right place to search for a hint or a solution.

    -

    The Discord server

    -

    For quick questions and chats you can also use the free Discord server dedicated to py4web. You could usually find many py4web developers hanging out in the channel.

    +

    The Discord server

    +

    For quick questions and chats you can also use the free Discord server dedicated to py4web. You could usually find +many py4web developers hanging out in the channel.

    -

    Tutorials and video

    +

    Tutorials and video

    There are many tutorials and videos available. Here are some of them:

    -

    The sources on GitHub

    -

    Last but not least, py4web is Open Source, with a BSD v3 license, hosted on GitHub at https://github.com/web2py/py4web. This means that you can read, study and experiment -with all of its internal details by yourself.

    +

    The sources on GitHub

    +

    Last but not least, py4web is Open Source, with a BSD v3 license, hosted on GitHub at https://github.com/web2py/py4web. This means that you can read, +study and experiment with all of its internal details by yourself.

    -

    Hints and tips

    +

    Hints and tips

    This paragraph is dedicated to preliminary hints, suggestions and tips that could be helpful to know before starting to learn py4web.

    -

    Prerequisites

    -

    In order to understand py4web you need at least a basic python knowledge. There are many books, courses and tutorials available on the web - choose what’s best for you. -The python’s decorators, in particular, are a milestone of any python web framework and you have to fully understand it.

    +

    Prerequisites

    +

    In order to understand py4web you need at least a basic python knowledge. There are many books, courses and tutorials available on the web - choose +what’s best for you. The python’s decorators, in particular, are a milestone of any python web framework and you have to fully understand it.

    -

    A modern python workplace

    -

    In the following chapters you will start coding on your computer. We suggest you to setup a modern python workplace if you plan to do it efficiently and safely. -Even for running simple examples and experimenting a little, we strongly suggest to use an Integrated Development Environment (IDE). This will make your programming experience much better, allowing syntax checking, linting and visual debugging. +

    A modern python workplace

    +

    In the following chapters you will start coding on your computer. We suggest you to setup a modern python workplace if you plan to do it efficiently +and safely. Even for running simple examples and experimenting a little, we strongly suggest to use an Integrated Development Environment (IDE). +This will make your programming experience much better, allowing syntax checking, linting and visual debugging. Nowadays there are two free and multi-platform main choices: Microsoft Visual Studio Code aka VScode and JetBrains PyCharm.

    When you’ll start to deal with more complex programs and need reliability, @@ -176,20 +186,21 @@

    A modern python workplace -

    Debugging py4web with VScode

    +

    Debugging py4web with VScode

    It’s quite simple to run and debug py4web within VScode.

    If you have installed py4web from source, you just need to open the main py4web folder (not the apps folder!) with VScode and add:

    "args": ["run", "apps"],
     "program": "your_full_path_to_py4web.py",
     
    -

    to the vscode launch.json configuration file. Note that if you’re using Windows the “your_full_path_to_py4web.py” parameter must be written using forward slash only, like +

    to the vscode launch.json configuration file. Note that if you’re using Windows the “your_full_path_to_py4web.py” parameter must be written using +forward slash only, like “C:/Users/your_name/py4web/py4web.py”.

    If you have instead installed py4web from pip, you need to:

    • open the apps folder with VScode

    • -
    • copy the standard py4web.py launcher inside it, but rename it to py4web-start.py in order to avoid import -errors later:

    • +
    • copy the standard py4web.py launcher inside it, but rename it to py4web-start.py in +order to avoid import errors later:

    #!/usr/bin/env python3
     from py4web.core import cli
    @@ -209,19 +220,20 @@ 

    Debugging py4web with VScode -

    Debugging py4web with PyCharm

    +

    Debugging py4web with PyCharm

    In PyCharm, if you should get gevent errors you need to enable Settings | Build, Execution, Deployment | Python Debugger | Gevent compatible.

    -

    How to contribute

    -

    We need help from everyone: support our efforts! You can just participate in the Google group trying to answer other’s questions, submit bugs using or create pull requests on the GitHub -repository.

    -

    If you wish to correct and expand this manual, or even translate it in a new foreign language, you can read all the needed information directly on the -specific README on GitHub.

    +

    How to contribute

    +

    We need help from everyone: support our efforts! You can just participate in the Google group trying to answer other’s questions, submit bugs using or +create pull requests on the GitHub repository.

    +

    If you wish to correct and expand this manual, or even translate it in a new foreign language, you can read all the needed information directly on +the specific README on GitHub.

    It’s really simple! Just change the .RST files in the /doc folder and create a Pull Request on the GitHub repository at https://github.com/web2py/py4web - you can even do it within your browser. -Once the PR is accepted, your changes will be written on the master branch, and will be reflected on the web pages / pdf / epub at the next output generation on the branch.

    +Once the PR is accepted, your changes will be written on the master branch, and will be reflected on the web pages / pdf / epub at the next output +generation on the branch.

    @@ -236,7 +248,7 @@

    How to contribute -

    © Copyright 2020, BSDv3 License.

    +

    © Copyright 2024, BSDv3 License.

    Built with Sphinx using a @@ -261,7 +273,7 @@

    How to contribute - v: 1.20230507.1 + v: 20240915
    diff --git a/apps/_documentation/static/en/chapter-03.html b/apps/_documentation/static/en/chapter-03.html index d0dcc190a..c5ac806d9 100644 --- a/apps/_documentation/static/en/chapter-03.html +++ b/apps/_documentation/static/en/chapter-03.html @@ -1,26 +1,28 @@ - + - + - Installation and Startup — py4web 1.20230507.1 documentation - - - - + Installation and Startup — py4web 20240915 documentation + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
    - 1.20230507.1 + 20240915
    @@ -59,11 +61,11 @@
  • Understanding the design
  • Supported platforms and prerequisites
  • Setup procedures
  • Upgrading
  • @@ -90,7 +92,7 @@
  • The Dashboard
  • -
  • Creating your first app
  • +
  • Creating an app
  • Fixtures
  • The Database Abstraction Layer (DAL)
  • The RestAPI
  • @@ -129,15 +131,15 @@
    -

    Installation and Startup

    +

    Installation and Startup

    -

    Understanding the design

    +

    Understanding the design

    Before everything else it is important to understand that unlike other web frameworks, is not only a python module that can be imported by apps. It is also a program that is in charge of starting some apps. For this reason you need two things:

      -
    • the py4web module (which you download from our web site, from pypi, from github)

    • -
    • one or more folders containing collections of apps you want to run.

    • +
    • The py4web module (which you download from our web site, from pypi or from github)

    • +
    • One or more folders containing collections of apps you want to run.

    py4web has command line options to create a folder with some example apps, to initialize an existing folder, and to add scaffolding apps to that folder. @@ -146,39 +148,55 @@

    Understanding the design -

    Supported platforms and prerequisites

    -

    PY4WEB runs fine on Windows, MacOS and Linux. Its only prerequisite is +

    Supported platforms and prerequisites

    +

    py4web runs fine on Windows, MacOS and Linux. Its only prerequisite is Python 3.7+, which must be installed in advance (except if you use binaries).

    -

    Setup procedures

    -

    There are four alternative ways of running py4web, with different level -of difficulty and flexibility. Let’s look at the pros and cons.

    -
    -

    Installing from binaries

    -

    This is not a real installation, because you just copy a bunch of files -on your system without modifying it anyhow. Hence this is the simplest -solution, especially for newbies or students, because it does not -require Python pre-installed on your system nor administrative rights. -On the other hand, it’s experimental, it could contain an old py4web -release and it is quite difficult to add other functionalities to it.

    -

    In order to use it you just need to download the latest Windows or MacOS -ZIP file from -this external repository. -Unzip it on a local folder and open a command line there. Finally run

    -
    py4web-start set_password
    -py4web-start run apps
    +

    Setup procedures

    +

    There are four alternative ways of installing py4web, we will guide +you through each of them and if you get stuck, reach +out to us.

    +
    +

    Installing from pip, using a virtual environment

    +

    A full installation of any complex python application like py4web will +surely modify the python environment of your system. In order to prevent +any unwanted change, it’s a good habit to use a python virtual +environment (also called virtualenv, see +here for an +introduction). This is a standard python feature; if you still don’t +know virtualenv it’s a good time to start its discovery!

    +

    Here are the instructions for creating the virtual environment, activating it, +and installing py4web in it:

    +
    +
    python3 -m venv venv
    +. venv/bin/activate
    +python -m pip install --upgrade py4web --no-cache-dir
    +python py4web setup apps
    +python py4web set_password
    +python py4web run apps
     
    -

    With this type of installation, remember to always use py4web-start -instead of ‘py4web’ or ‘py4web.py’ in the following documentation.

    -

    Notice the binaries many not correspond to the latest master -or the latest stable branch of py4web although we do our best to -keep them up to date.

    +

    Starting py4web is same with or without a virtual environment +python py4web run apps

    +
    -
    -

    Installing from pip

    -

    Using pip is the standard installation procedure for py4web, since it will +

    +

    Installing from pip, without virtual environment

    +

    pip is the basic installation procedure for py4web, it will quickly install the latest stable release of py4web.

    From the command line

    python3 -m pip install --upgrade py4web --no-cache-dir --user
    @@ -197,28 +215,15 @@ 

    Installing from pip -

    Installing using a virtual environment

    -

    A full installation of any complex python application like py4web will -surely modify the python environment of your system. In order to prevent -any unwanted change, it’s a good habit to use a python virtual -environment (also called virtualenv, see -here for an -introduction). This is a standard python feature; if you still don’t -know virtualenv it’s a good time to start its discovery!

    -

    Here are the instructions for creating the virtual environment, activating it, -and installing py4web in it:

    -
    python3 -m venv venv
    -. venv/bin/activate
    -python -m pip install --upgrade py4web --no-cache-dir
    +–user option by mistake, then you can run the needed commands like this

    +
    python3 py4web.py setup apps
    +python3 py4web.py set_password
    +python3 py4web.py run apps
     
    -

    The instructions for starting and running py4web are the same with or without a virtual environment.

    -

    Installing from source (globally)

    +

    Installing from source (globally)

    This is the traditional way for installing a program, but it works only on Linux and MacOS (Windows does not normally support the make utility). All the requirements will be installed on the @@ -229,6 +234,8 @@

    Installing from source (globally) -

    Installing from source (locally)

    +

    Installing from source (locally)

    In this way all the requirements will be installed or upgraded on the system’s path, but py4web itself will only be copied on a local folder. This is especially useful if you already have a @@ -254,7 +261,7 @@

    Installing from source (locally) -
    ./py4web.py setup apps
    +
    ./py4web.py setup apps
     ./py4web.py set_password
     ./py4web.py run apps
     
    @@ -262,7 +269,7 @@

    Installing from source (locally)

    -

    Upgrading

    +

    Upgrading

    If you installed py4web from pip you can simple upgrade it with

    python3 -m pip install --upgrade py4web
     
    @@ -285,7 +313,7 @@

    Upgrading
    py4web setup apps
    +
    py4web setup <path to apps_folder>
     

    in order to re-install them. This is a safety precaution, in case you @@ -297,12 +325,9 @@

    Upgrading -

    First run

    +

    First run

    Running py4web using any of the previous procedure should produce an output like this:

    -
    py4web run apps
    -
    -
    _images/first_run.png

    Generally apps is the name of the folder where you keep all your apps, and can be explicitly set wit the run command. @@ -319,7 +344,7 @@

    First run
    http://localhost:8000
     http://localhost:8000/_dashboard
     http://localhost:8000/{yourappname}/index
    @@ -339,7 +364,7 @@ 

    First run -

    Command line options

    +

    Command line options

    py4web provides multiple command line options which can be listed by running it without any argument

    # py4web
    @@ -349,7 +374,7 @@ 

    Command line optionsYou can have additional help for a specific command line option by running it with the –help or -h argument.

    -

    call command option

    +

    call command option

    # py4web call -h
     Usage: py4web.py call [OPTIONS] APPS_FOLDER FUNC
     
    @@ -370,7 +395,7 @@ 

    Command line options

    -

    new_app command option

    +

    new_app command option

    # py4web new_app -h
     Usage: py4web.py new_app [OPTIONS] APPS_FOLDER APP_NAME
     
    @@ -388,24 +413,27 @@ 

    Command line options

    -

    run command option

    +

    run command option

    # py4web run -h
     Usage: py4web.py run [OPTIONS] APPS_FOLDER
     
    -  Run all the applications on apps_folder
    +  Run the applications on apps_folder
     
     Options:
       -Y, --yes                     No prompt, assume yes to questions
                                     [default: False]
     
    -  -H, --host TEXT               Host name  [default: 127.0.0.1]
    +  -H, --host TEXT               Host listening IP [default: 127.0.0.1]
       -P, --port INTEGER            Port number  [default: 8000]
    +  -A, --app_names TEXT          List of apps to run, comma separated (all if omitted or
    +                                empty)
       -p, --password_file TEXT      File for the encrypted password  [default:
                                     password.txt]
    -
    -  -s, --server [default|wsgiref|tornado|gunicorn|gevent|waitress|
    -                geventWebSocketServer|wsgirefThreadingServer|rocketServer]
    -                                server to use  [default: default]
    +  -Q, --quiet                   Suppress server output
    +  -R, --routes                  Write apps routes to file
    +  -s, --server                  [default|wsgiref|tornado|gunicorn|gevent|waitress|gunicorn|gunicornGevent|gevent|
    +                                geventWebSocketServer|geventWs|wsgirefThreadingServer|wsgiTh|rocketServer]
    +                                Web server to use
       -w, --number_workers INTEGER  Number of workers  [default: 0]
       -d, --dashboard_mode TEXT     Dashboard mode: demo, readonly, full, none
                                     [default: full]
    @@ -422,9 +450,18 @@ 

    Command line options

    +

    The app_names option lets you filter which specific apps you want to serve (comma separated). If absent or empty +all the apps in the APPS_FOLDER will be run.

    +

    By default (for security reasons) the py4web framework will listen only on 127.0.0.1, i.e. localhost. +If you need to reach it from other machines you must specify the host option, +like py4web run --host 0.0.0.0 apps.

    +

    The url_prefix option is useful for routing at the py4web level. It allows mapping to multiple versions of py4web +running on different ports as long as the url_prefix and port match the location. For example +py4web run --url_prefix=/abracadabra --port 8000 apps.

    By default py4web will automatically reload an application upon any changes to the python files of that application. The reloading will occur on any first incoming request to the application that has been changed (lazy-mode). If you prefer an immediate reloading (sync-mode), use @@ -446,7 +483,7 @@

    Command line options

    -

    set_password command option

    +

    set_password command option

    # py4web set_password -h
     Usage: py4web.py set_password [OPTIONS]
     
    @@ -483,7 +520,7 @@ 

    Command line options

    -

    setup command option

    +

    setup command option

    # py4web setup -h
     Usage: py4web.py setup [OPTIONS] APPS_FOLDER
     
    @@ -502,7 +539,7 @@ 

    Command line options

    -

    shell command option

    +

    shell command option

    # py4web shell -h
     Usage: py4web.py shell [OPTIONS] APPS_FOLDER
     
    @@ -525,7 +562,7 @@ 

    Command line options

    -

    version command option

    +

    version command option

    # py4web version -h
     Usage: py4web.py version [OPTIONS]
     
    @@ -541,13 +578,13 @@ 

    Command line options

    -

    Special installations

    +

    Special installations

    There are special cases in which you cannot or don’t want to use one of the generic installation instructions we’ve already described. There is a special folder called deployment_tools in the py4web repository that collects some special recipes. They are briefly described here, along with some tips and tricks.

    -

    HTTPS

    +

    HTTPS

    To use https with the build-in web server (Rocket3) these are the steps:

    -

    WSGI

    +

    WSGI

    py4web is a standard WSGI application. So, if a full program installation it’s not feasible you can simply run py4web as a WSGI app. For example, using gunicorn-cli, create a python file:

    @@ -594,7 +631,7 @@

    WSGI

    The wsgi function takes arguments with the same name as the command line arguments.

    -

    Deployment on GCloud (aka GAE - Google App Engine)

    +

    Deployment on GCloud (aka GAE - Google App Engine)

    Login into the Gcloud console and create a new project. You will obtain a project id that looks like “{project_name}-{number}”.

    @@ -638,21 +675,22 @@

    Deployment on GCloud (aka GAE - Google App Engine)main.py.

    -

    Deployment on PythonAnywhere.com

    +

    Deployment on PythonAnywhere.com

    Watch the YouTube video and follow the detailed tutorial . The bottle_app.py script is in py4web/deployment_tools/pythonanywhere.com/bottle_app.py

    -

    Deployment on Docker/Podman

    +

    Deployment on Docker/Podman

    On deployment_tools/docker there is a simple Dockerfile for quickly running a py4web container. There is also -a docker-compose.yml file for setting up a more complex multi-container with PostgreSQL.

    +a docker-compose.yml file for setting up a more complex multi-container with PostgreSQL. +A ready docker example based on the Scaffold application can be cloned from this repository <https://github.com/macneiln/docker-py4web-scaffold>

    Note that you can use them also with Podman, which has the advantage of does not requiring sudo and does not running any background daemon.

    -

    Deployment on Ubuntu

    +

    Deployment on Ubuntu

    On deployment_tools/ubuntu there is a bash script tested with Ubuntu Server 20.04.03 LTS. It uses nginx and self-signed certificates. It optionally manage iptables, too.

    @@ -670,7 +708,7 @@

    Deployment on Ubuntu
    -

    © Copyright 2020, BSDv3 License.

    +

    © Copyright 2024, BSDv3 License.

    Built with
    Sphinx using a @@ -695,7 +733,7 @@

    Deployment on Ubuntu - v: 1.20230507.1 + v: 20240915
    diff --git a/apps/_documentation/static/en/chapter-04.html b/apps/_documentation/static/en/chapter-04.html index a5683c28c..891a70dda 100644 --- a/apps/_documentation/static/en/chapter-04.html +++ b/apps/_documentation/static/en/chapter-04.html @@ -1,29 +1,31 @@ - + - + - The Dashboard — py4web 1.20230507.1 documentation - - - - + The Dashboard — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + - + @@ -40,7 +42,7 @@
    - 1.20230507.1 + 20240915
    @@ -60,7 +62,7 @@
  • Login into the Dashboard
  • -
  • Creating your first app
  • +
  • Creating an app
  • Fixtures
  • The Database Abstraction Layer (DAL)
  • The RestAPI
  • @@ -99,13 +101,13 @@
    -

    The Dashboard

    +

    The Dashboard

    The Dashboard is the standard web based IDE; you will surely use it extensively to manage the applications and check your databases. Looking at its interface is a good way to start exploring py4web and its components.

    -

    The main Web page

    +

    The main Web page

    When you run the standard py4web program, it starts a web server with a main web page listening on http://127.0.0.1:8000 (which means that it is listening on the TCP port 8000 on your local PC, using the HTTP protocol).

    @@ -125,7 +127,7 @@

    The main Web page -

    Login into the Dashboard

    +

    Login into the Dashboard

    Pressing the Dashboard button will forward you to the Dashboard login. Here you must insert the password that you’ve already setup (see set_password command option). @@ -167,13 +169,13 @@

    Login into the Dashboard - +


    -

    © Copyright 2020, BSDv3 License.

    +

    © Copyright 2024, BSDv3 License.

    Built with Sphinx using a @@ -198,7 +200,7 @@

    Login into the Dashboard - v: 1.20230507.1 + v: 20240915
    diff --git a/apps/_documentation/static/en/chapter-05.html b/apps/_documentation/static/en/chapter-05.html index fbe9496de..ddb242e56 100644 --- a/apps/_documentation/static/en/chapter-05.html +++ b/apps/_documentation/static/en/chapter-05.html @@ -1,25 +1,27 @@ - + - + - Creating your first app — py4web 1.20230507.1 documentation - - - - + Creating an app — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
    - 1.20230507.1 + 20240915
    @@ -56,7 +58,7 @@
  • Help, resources and hints
  • Installation and Startup
  • The Dashboard
  • -
  • Creating your first app
      +
    • Creating an app
      • From scratch
      • Static web pages
      • Dynamic Web Pages
      • Fixtures
      • @@ -98,7 +101,7 @@
        • - +
        • Edit on GitHub
        • @@ -108,10 +111,10 @@
          -
          -

          Creating your first app

          +
          +

          Creating an app

          -

          From scratch

          +

          From scratch

          Apps can be created using the dashboard or directly from the filesystem. Here, we are going to do it manually, as the Dashboard is already described in its own chapter.

          @@ -149,7 +152,7 @@

          From scratch -

          Static web pages

          +

          Static web pages

          To expose static web pages you simply need to create a static subfolder, and any file in there will be automatically published:

          mkdir apps/myapp/static
          @@ -169,7 +172,7 @@ 

          Static web pages -

          Dynamic Web Pages

          +

          Dynamic Web Pages

          To create a dynamic page, you must create a function that returns the page content. For example edit the myapp/__init__.py as follows:

          import datetime
          @@ -201,7 +204,7 @@ 

          Dynamic Web Pages -

          On return values

          +

          On return values

          py4web actions should return a string or a dictionary. If they return a dictionary you must tell py4web what to do with it. By default py4web will serialize it into json. For example edit __init__.py again and @@ -223,7 +226,7 @@

          On return values -

          Routes

          +

          Routes

          It is possible to map patterns in the URL into arguments of the function. For example:

          @action('color/<name>')
          @@ -265,7 +268,7 @@ 

          Routes

          -

          The request object

          +

          The request object

          From py4web you can import request

          from py4web import request
           
          @@ -288,7 +291,7 @@ 

          The requestWhich you can use the code to identify the name and the folder used for the app.

          -

          Templates

          +

          Templates

          In order to use a yatl template you must declare it. For example create a file apps/myapp/templates/paint.html that contains:

          <html>
            <head>
          @@ -345,7 +348,7 @@ 

          Templates -

          The _scaffold app

          +

          The _scaffold app

          Most of the times, you do not want to start writing code from scratch. You also want to follow some sane conventions outlined here, like not putting all your code into __init__.py. PY4WEB provides a @@ -359,32 +362,7 @@

          The _scaffold app_scaffold app:

          -
          ├── __init__.py          # imports everything else
          -├── common.py            # defines useful objects
          -├── controllers.py       # your actions
          -├── databases            # your sqlite databases and metadata
          -    │   └── README.md
          -├── models.py            # your pyDAL table model
          -├── settings.py          # any settings used by the app
          -├── settings_private.py  # (optional) settings that you want to keep private
          -├── static               # static files
          -│   ├── README.md
          -│   ├── css              # CSS files, we ship bulma because it is JS agnostic
          -│   │   └── no.css       # we used bulma.css in the past
          -│   ├── favicon.ico
          -│   └── js               # JS files, we ship with these but you can replace them
          -│       ├── utils.js
          -├── tasks.py
          -├── templates            # your templates go here
          -│   ├── README.md
          -│   ├── auth.html        # the auth page for register/logic/etc (uses vue)
          -│   ├── generic.html     # a general purpose template
          -│   ├── index.html
          -│   └── layout.html      # a bulma layout example
          -└── translations         # internationalization/pluralization files go here
          -    └── it.json          # py4web internationalization/pluralization files are in JSON, this is an italian example
          -
          -
          +_images/scaffold_tree.png

          The scaffold app contains an example of a more complex action:

          from py4web import action, request, response, abort, redirect, URL
           from yatl.helpers import A
          @@ -401,8 +379,8 @@ 

          The _scaffold app -
        • request, response, abort are defined by -which is a fast bottlepy spin-off.

        • +
        • request, response, abort are defined by ombott +which is a minimal and fast bottlepy spin-off.

        • redirect and URL are similar to their web2py counterparts

        • helpers (A, DIV, SPAN, IMG, etc) must be imported from yatl.helpers . They work pretty much as in web2py

        • @@ -422,7 +400,7 @@

          The _scaffold app -

          Copying the _scaffold app

          +

          Copying the _scaffold app

          The scaffold app is really useful, and you will surely use it a lot as a starting point for testing and even developing full features new apps.

          It’s better not to work directly on it: always create new apps copying it. @@ -440,7 +418,7 @@

          The _scaffold app -

          Watch for files change

          +

          Watch for files change

          As described in the run command option, Py4web facilitates a development server’s setup by automatically reloads an app when its Python source files change (by default). @@ -487,6 +465,42 @@

          Watch for files change

          +
          +

          Domain-mapped apps

          +

          In production environments it is often required to have several apps being +served by a single py4web server, where different apps are mapped to +different domains.

          +

          py4web can easily handle running multiple apps, but there is no build-in +mechanism for mapping domains to specific applications. Such mapping needs +to be done externally to py4web – for instance using a web reverse-proxy, +such as nginx.

          +

          While nginx or other reverse-proxies are also useful in production +environments for handling SSL termination, caching and other uses, +we cover only the mapping of domains to py4web applications here.

          +

          An example nginx configuration for an application myapp mapped to +a domain myapp.example.com might look like that:

          +
          server {
          +   listen 80;
          +   server_name myapp.example.com;
          +   proxy_http_version 1.1;
          +   proxy_set_header Host $host;
          +   proxy_set_header X-PY4WEB-APPNAME /myapp;
          +   location / {
          +      proxy_pass http://127.0.0.1:8000/myapp$request_uri;
          +   }
          +}
          +
          +
          +

          This is an example server block of nginx configuraiton. One would have to create a separate such block for each app/each domain being served by py4web server. Note some important aspects:

          +
            +
          • server_name defines the domain mapped to the app myapp,

          • +
          • proxy_http_version 1.1; directive is optional, but highly recommended (otherwise nginx uses HTTP 1.0 to talk to the backend-server – here py4web – and it creates all kinds of issues with buffering and otherwise),

          • +
          • proxy_set_header Host $host; directive ensures that the correct Host is passed to py4web – here myapp.example.com

          • +
          • proxy_set_header X-PY4WEB-APPNAME /myapp; directive ensures that py4web (and ombott) knows which app to serve and also that this application is domain-mapped – pay specific attention to the slash (/) in front of the myapp name – it is required to ensure correct parsing of URLs on ombott level,

          • +
          • finally proxy_pass http://127.0.0.1:8000/myapp$request_uri; ensures that the request is passed in its entirity ($request_uri) to py4web server (here: 127.0.0.1:8000) and the correct app (/myapp).

          • +
          +

          Such configuration ensures that all URL manipulation inside ombott and py4web - especially in modules such as Auth, Form, and Grid are done correctly using the domain to which the app is mapped to.

          +
          @@ -500,7 +514,7 @@

          Watch for files change
          -

          © Copyright 2020, BSDv3 License.

          +

          © Copyright 2024, BSDv3 License.

          Built with
          Sphinx using a @@ -525,7 +539,7 @@

          Watch for files change - v: 1.20230507.1 + v: 20240915
          diff --git a/apps/_documentation/static/en/chapter-06.html b/apps/_documentation/static/en/chapter-06.html index 30ca12de2..517ccee73 100644 --- a/apps/_documentation/static/en/chapter-06.html +++ b/apps/_documentation/static/en/chapter-06.html @@ -1,30 +1,32 @@ - + - + - Fixtures — py4web 1.20230507.1 documentation - - - - + Fixtures — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + - + @@ -40,7 +42,7 @@
          - 1.20230507.1 + 20240915
          @@ -56,7 +58,7 @@
        • Help, resources and hints
        • Installation and Startup
        • The Dashboard
        • -
        • Creating your first app
        • +
        • Creating an app
        • Fixtures
          • Using Fixtures
          • The Template fixture
          • @@ -120,7 +122,7 @@
            -

            Fixtures

            +

            Fixtures

            A fixture is defined as “a piece of equipment or furniture which is fixed in position in a building or vehicle”. In our case a fixture is something attached to the action that processes an HTTP request in order @@ -138,7 +140,8 @@

            FixturesCustom fixtures paragraph.

            -

            Using Fixtures

            +

            Using Fixtures

            As we’ve seen in the previous chapter, fixtures are the arguments of the decorator @action.uses(...). You can specify multiple fixtures in one decorator or you can have multiple decorators.

            @@ -174,7 +177,7 @@

            Using Fixtures -

            The Template fixture

            +

            The Template fixture

            PY4WEB by default uses the YATL template language and provides a fixture for it.

            from py4web import action
            @@ -233,10 +236,11 @@ 

            The Template fixture

            -

            The Inject fixture

            +

            The Inject fixture

            The Inject fixture is used for passing variables (and even python functions) to templates. Here is a simple example:

            -
            my_var = "Example variable to be passed to a Template"
            +
            from py4web.utils.factories import Inject
            +my_var = "Example variable to be passed to a Template"
             
             ...
             
            @@ -249,7 +253,7 @@ 

            The Inject fixtureUsing Inject in the YATL chapter.

            -

            The Translator fixture

            +

            The Translator fixture

            Here is an example of usage:

            from py4web import action, Translator
             import os
            @@ -286,6 +290,22 @@ 

            The Translator fixturereturn str(T("You have been here {n} times").format(n=counter))

            +

            If the T fixture is to be used from inside a template you may want to pass it to the template:

            +
            @action('index')
            +@action.uses("index.html", session, T)
            +def index():
            +    return dict(T=T)
            +
            +
            +

            Or perhaps inject (same effect as above)

            +
            from py4web.utils.factories import Inject
            +
            +@action('index')
            +@action.uses("index.html", session, Inject(T=T)
            +def index():
            +    return dict()
            +
            +

            Now create the following translation file translations/en.json:

            {"You have been here {n} times":
               {
            @@ -324,9 +344,30 @@ 

            The Translator fixture

            Set your browser preference to Italian: now the messages will be automatically translated to Italian.

            +

            Notice there is an UI in the Dashboard for creating, updating, and updating translation files. +It can be easily reached via the button i18n+p11n:

            +_images/dashboard_i18n_btn.png +

            that leads to the following interface:

            +_images/dashboard_i18n_ui.png +

            More details can be found here: https://github.com/web2py/pluralize

            +

            If you want to force an action to use language defined somewhere else, for example from a session variable, you can do:

            +
            @action('index')
            +@action.uses("index.html", session, T)
            +def index():
            +    T.select(session.get("lang", "it"))
            +    return dict(T=T)
            +
            +
            +

            If you want all of your action to use the same pre-defined language and ignore browser preferences, +you have to redefine the select method for the T instance:

            +
            T.on_request = lambda *_: T.local.__dict__.update(tag="it", language=T.languages["it"])
            +
            +
            +

            This is to be done outside any action and will apply to all actions. Action will still need to declare +action.uses(T) else the behavior is undefined.

            -

            The Flash fixture

            +

            The Flash fixture

            It is common to want to display “alerts” to the users. Here we refer to them as flash messages. There is a little more to it than just displaying a message to the view, because flash messages:

            @@ -349,13 +390,7 @@

            The Flash fixture
            ...
            -<div id="py4web-flash"></div>
            -...
            -<script src="js/utils.js"></script>
            -[[if globals().get('flash'):]]
            -<script>utils.flash([[=XML(flash)]]);</script>
            -[[pass]]
            +
            <flash-alerts class="padded" data-alert="[[=globals().get('flash','')]]"></flash-alerts>
             

            By setting the value of the message in the flash helper, a flash @@ -369,7 +404,7 @@

            The Flash fixture
            utils.flash({'message': 'hello world', 'class': 'info'});
            +
            Q.flash({'message': 'hello world', 'class': 'info'});
             

            py4web defaults to an alert class called info and most CSS @@ -379,7 +414,7 @@

            The Flash fixture -

            The Session fixture

            +

            The Session fixture

            Simply speaking, a session can be defined as a way to preserve information that is desired to persist throughout the user’s interaction with the web site or web application. In other words, sessions render the stateless HTTP connection a stateful one.

            @@ -450,21 +485,24 @@

            The Session fixture -

            Client-side session in cookies

            +

            Client-side session in cookies

            By default the session object is stored inside a cookie called appname_session. It’s a JWT, hence encoded in a URL-friendly string -format and signed using the provided secret for preventing tampering. -Notice that it’s not encrypted (in fact it’s quite trivial to read its -content from http communications or from disk), so do not place any -sensitive information inside, and use a complex secret. -If the secret changes existing sessions are invalidated. +format and signed using the provided secret for preventing tampering.

            +
            +

            Warning

            +

            Data embedded in cookies is signed, not encrypted! In fact it’s quite +trivial to read its content from http communications or from disk, so +do not place any sensitive information inside, and use a complex secret.

            +
            +

            If the secret changes existing sessions are invalidated. If the user switches from HTTP to HTTPS or vice versa, the user session is also invalidated. Session in cookies have a small size limit (4 kbytes after being serialized and encoded) so do not put too much into them.

            -

            Server-side session in memcache

            +

            Server-side session in memcache

            Requires memcache installed and configured.

            import memcache, time
             conn = memcache.Client(['127.0.0.1:11211'], debug=0)
            @@ -473,7 +511,7 @@ 

            Server-side session in memcache -

            Server-side session in Redis

            +

            Server-side session in Redis

            Requires Redis installed and configured.

            import redis
             conn = redis.Redis(host='localhost', port=6379)
            @@ -488,7 +526,7 @@ 

            Server-side session in Redis -

            Server-side session in database

            +

            Server-side session in database

            from py4web import Session, DAL
             from py4web.utils.dbstore import DBStore
             db = DAL('sqlite:memory')
            @@ -512,7 +550,7 @@ 

            Server-side session in database -

            Server-side session anywhere

            +

            Server-side session anywhere

            You can easily store sessions in any place you want. All you need to do is provide to the Session object a storage object with both get and set methods. For example, imagine you want to store @@ -543,14 +581,18 @@

            Server-side session anywhere -

            Sharing sessions

            -

            Imagine you have an app “app1” which uses a session and an app “app2” that wants to share a session with app1. Assuming they use sessons in cookies, “app2” would use:

            +

            Sharing sessions

            +

            Imagine you have an app “app1” which uses a session and an app “app2” that wants to share a session with app1. Assuming they use sessions in cookies, +“app2” would use:

            session = Session(secret=settings.SESSION_SECRET_KEY,
                               name="app1_session")
             
            -

            The name tells app2 to use the cookie “app1_session” from app1. Notice it is important that the secret is the same as app1’s secret. If using a session in db, then app2 must be using the same db as app1. It is up to the user to make sure that the data stored in the session and shared between the two apps are consistent and we strongly recommend that only app1 writes to the session, unless the share one and the same database.

            -

            Notice that it is possible for one app to handle multiple sessions. For example one session may be its own, and another may be used exclusively to read data from another app (app1) running on the same server:

            +

            The name tells app2 to use the cookie “app1_session” from app1. Notice it is important that the secret is the same as app1’s secret. If using a session +in db, then app2 must be using the same db as app1. It is up to the user to make sure that the data stored in the session and shared between the two apps +are consistent and we strongly recommend that only app1 writes to the session, unless the share one and the same database.

            +

            Notice that it is possible for one app to handle multiple sessions. For example one session may be its own, and another may be used exclusively to read +data from another app (app1) running on the same server:

            session_app1 = Session(secret=settings.SESSION_SECRET_KEY,
                                    name="app1_session")
             ...
            @@ -561,8 +603,8 @@ 

            Sharing sessions -

            The Condition fixture

            -

            Some times you want to restrict access to an action based on a +

            The Condition fixture

            +

            Sometimes you want to restrict access to an action based on a given condition. For example to enforce a workflow:

            @action("step1")
             @action.uses(session)
            @@ -600,26 +642,22 @@ 

            The Condition fixture
            Condition(cond, on_false=lambda: redirect(URL('step1')))
             

            -

            You can use condition to check permissions. For example, assuming you are using -Tags as explained in chapter 13 and you are giving group memberships to users, -then you can require that users action have specific group membership:

            +

            You can use condition to check permissions. For example, if you +are giving group memberships to users using Tags (it will be explained +later on the Authorization using Tags chapter), then you can +require that users action have specific group membership:

            groups = Tags(db.auth_user)
             
            -def requires_membership(group_name):
            -    return Condition(
            -       lambda: group_name in groups.get(auth.user_id),
            -       exception=HTTP(404)
            -    )
            -
             @action("payroll")
            -@action.uses(auth, requires_membership("employees"))
            +@action.uses(auth,
            +             Condition(lambda: 'employees' in groups.get(auth.user_id), on_false=lambda: redirect('index')))
             def payroll():
                 return
             
            -

            The URLsigner fixture

            +

            The URLsigner fixture

            A signed URL is a URL that provides limited permission and time to make an HTTP request by containing authentication information in its query string. The typical usage is as follows:

            @@ -643,7 +681,7 @@

            The URLsigner fixture

            -

            The DAL fixture

            +

            The DAL fixture

            We have already used the DAL fixture in the context of sessions but maybe you want direct access to the DAL object for the purpose of accessing the database, not just sessions.

            @@ -675,7 +713,7 @@

            The DAL fixtureon_success and rolls back on_error.

            -

            The Auth fixture

            +

            The Auth fixture

            auth and auth.user are both fixtures that depend on session and db. Their role is to provide the action with authentication information.

            @@ -701,6 +739,19 @@

            The Auth fixtureauth_user table is defined before calling auth.enable() +the provided table will be used.

            +

            It is also possible to add extra_fields to the auth_user table, +for example:

            +
            extra_fields = [
            +   Field("favorite_color"),
            +]
            +auth = Auth(session, db, extra_fields=extra_fields)
            +
            +
            +

            In any case, we recommend not to pollute the auth_user table with +extra fields but, instead, to use one of more additional custom +tables that reference users and store the required information.

            The auth object exposes the method:auth.enable() which registers multiple actions including {appname}/auth/login. It requires the presence of the auth.html template and the @@ -730,7 +781,7 @@

            The Auth fixture -

            Caveats about fixtures

            +

            Caveats about fixtures

            Since fixtures are shared by multiple actions you are not allowed to change their state because it would not be thread safe. There is one exception to this rule. Actions can change some attributes of database @@ -764,7 +815,7 @@

            Caveats about fixtures

            -

            Custom fixtures

            +

            Custom fixtures

            A fixture is an object with the following minimal structure:

            from py4web.core import Fixture
             
            @@ -860,7 +911,7 @@ 

            Custom fixtures -

            Multiple fixtures

            +

            Multiple fixtures

            As previously stated, it’s generally not important the order you use to specify the fixtures but it’s mandatory that you always place the template as the first one. Consider this:

            @@ -898,7 +949,7 @@

            Multiple fixtures -

            Caching and Memoize

            +

            Caching and Memoize

            py4web provides a cache in RAM object that implements the last recently used (LRU) algorithm. It can be used to cache any function via a decorator:

            @@ -923,16 +974,16 @@

            Caching and Memoize -

            Convenience Decorators

            +

            Convenience Decorators

            The _scaffold application, in common.py defines two special convenience decorators:

            -
            @unauthenticated
            +
            @unauthenticated()
             def index():
                 return dict()
             

            and

            -
            @authenticated
            +
            @authenticated()
             def index():
                 return dict()
             
            @@ -955,14 +1006,14 @@

            Convenience Decorators

            -

            Using the DAL “stand-alone”

            +

            Using the DAL “stand-alone”

            pyDAL is an independent python package. As such, it can be used without the web2py/py4web environment; you just need to install it with pip. Then import the pydal module when needed:

            @@ -443,7 +454,7 @@

            Using the DAL “stand-alone” -

            Experiment with the py4web shell

            +

            Experiment with the py4web shell

            You can also experiment with the pyDAL API using the py4web shell, that is available using the shell command option.

            @@ -451,15 +462,17 @@

            Experiment with the py4web shell>>> are also directly executable via a py4web shell.

            -

            This is a simple example, using the provided examples app:

            -
            >>> from py4web import DAL, Field
            ->>> from apps.examples import db
            +

            This is a simple example, using the provided showcase app:

            +
            >>> from apps.showcase.examples.models import db
             >>> db.tables()
            -['auth_user', 'auth_user_tag_groups', 'person', 'superhero', 'superpower', 'tag', 'product', 'thing']
            +['auth_user', 'auth_user_tag_groups', 'person', 'superhero', 'superpower', 'tag', 'thing', 'user_token', 'dummy']
             >>> rows = db(db.superhero.name != None).select()
             >>> rows.first()
             <Row {'id': 1, 'tag': <Set ("tag"."superhero" = 1)>, 'name': 'Superman', 'real_identity': 1}>
            @@ -470,8 +483,21 @@ 

            Experiment with the py4web shell +

            Using the dashboard app with databases

            +

            Generally you can use the dashboard app for viewing and modifying the databases +of a particular app. However this is not bulletproof, so for +security reason this by default is not applied to the showcase app. +But if your installation is local (not exposed to public networks), you can enable it +by simply adding to the file``apps/showcase/__init__.py`` the line:

            +
            from .examples.models import db
            +
            +
            +

            This allow you to look graphically inside the showcase application database:

            +_images/example_db.png +

            -

            DAL constructor

            +

            DAL constructor

            Basic use:

            >>> db = DAL('sqlite://storage.sqlite')
             
            @@ -495,7 +521,7 @@

            DAL constructor -

            DAL signature

            +

            DAL signature

            DAL(uri='sqlite://dummy.db',
                 pool_size=0,
                 folder=None,
            @@ -524,7 +550,7 @@ 

            DAL signature -

            Connection strings (the uri parameter)

            +

            Connection strings (the uri parameter)

            A connection with the database is established by creating an instance of the DAL object:

            db = DAL('sqlite://storage.sqlite')
            @@ -609,7 +635,11 @@ 

            Connection strings (the uri parameter)
          • in SQLite the database consists of a single file. If it does -not exist, it is created. This file is locked every time it is accessed.

          • +not exist, it is created. This file is locked every time it is accessed. +In addition to the file ‘storage.sqlite’ that contains the data, there will +be also a sql.log file plus one additional file called longhash_tablename.table +for every table definition. The table definition files are used during migrations; +in case of problems they could be deleted (they’ll be automatically recreated).

          • in the case of MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Ingres and Informix the database “test” must be created outside py4web. Once the connection is established, py4web will create, alter, and drop @@ -645,7 +675,7 @@

            Connection strings (the uri parameter) -

            Connection pooling

            +

            Connection pooling

            A common argument of the DAL constructor is the pool_size; it defaults to zero.

            As it is rather slow to establish a new database connection for each @@ -670,7 +700,7 @@

            Connection pooling -

            Connection failures (attempts parameter)

            +

            Connection failures (attempts parameter)

            If py4web fails to connect to the database it waits 1 second and by default tries again up to 5 times before declaring a failure. In case of connection pooling it is possible that a pooled connection that stays @@ -679,7 +709,7 @@

            Connection failures (attempts parameter) -

            Lazy Tables

            +

            Lazy Tables

            Setting lazy_tables = True provides a major performance boost (but not with py4web). It means that table creation is deferred until the table is actually referenced.

            @@ -690,9 +720,9 @@

            Lazy Tables -

            Model-less applications

            -

            In py4web the code defined outside of actions (where normally DAL tables -are defined) is only executed at startup.

            +

            Model-less applications

            +

            Normally in py4web the code that define DAL tables lives in the file +models.py, hence it’s only executed at startup because it’s outside of actions.

            However, it is possible to define DAL tables on demand inside actions. This is referred to as “model-less” development by the py4web community.

            To use the “model-less” approach, you take responsibility for doing @@ -702,7 +732,7 @@

            Model-less applicationsmodels.py file.

          • -

            Replicated databases

            +

            Replicated databases

            The first argument of DAL(...) can be a list of URIs. In this case py4web tries to connect to each of them. The main purpose for this is to deal with multiple database servers and distribute the workload among @@ -715,7 +745,7 @@

            Replicated databases

            -

            Reserved keywords

            +

            Reserved keywords

            check_reserved tells the constructor to check table names and column names against reserved SQL keywords in target back-end databases. check_reserved defaults to None.

            @@ -765,7 +795,7 @@

            Reserved keywords -

            Database quoting and case settings

            +

            Database quoting and case settings

            Quoting of SQL entities are enabled by default in DAL, that is:

            entity_quoting = True

            This way identifiers are automatically quoted in SQL generated by DAL. @@ -788,7 +818,7 @@

            Database quoting and case settings -

            Making a secure connection

            +

            Making a secure connection

            Sometimes it is necessary (and advised) to connect to your database using secure connection, especially if your database is not on the same server as your application. In this case you need to pass additional @@ -806,18 +836,24 @@

            Making a secure connection -

            Other DAL constructor parameters

            +

            Other DAL constructor parameters

            -

            Database folder location

            +

            Database folder location

            folder sets the place where migration files will be created (see -Migrations for details). -It is also used for SQLite databases. Automatically set within py4web. -Set a path when using DAL outside py4web.

            +Migrations for details). By default it’s automatically set within py4web on the same +folder of the database itself, but you have to specify it when using DAL outside py4web.

            +

            Note that for SQLite databases it’s normally necessary, +otherwise you’ll implicitly choose an in memory database (where folder and +migrations don’t have any sense). So these constructors have the same meaning:

            +
            db = DAL('sqlite://storage.sqlite') # folder parameter not specified
            +db = DAL('sqlite:memory')           # in memory database
            +
            +
            -

            Default migration settings

            +

            Default migration settings

            The DAL constructor migration settings are booleans affecting defaults -and global behaviour.

            +and global behaviour (again, see Migrations for details)

            migrate = True sets default migrate behavior for all tables

            fake_migrate = False sets default fake_migrate behavior for all tables

            @@ -826,13 +862,13 @@

            Default migration settings -

            commit and rollback

            +

            commit and rollback

            The insert, truncate, delete, and update operations aren’t actually committed until py4web issues the commit command. The create and drop operations may be executed immediately, depending on the database engine.

            If you pass db in an action.uses decorator, you don’t need to call -commit in the controller, it is done for you. (Also, if you use +commit in the controller, it is automatically done for you (also, if you use authenticated or unauthenticated decorator.)

            Tip

            @@ -880,10 +916,10 @@

            commit

            -

            Table constructor

            +

            Table constructor

            Tables are defined in the DAL via define_table.

            -

            define_table signature

            +

            define_table signature

            The signature for define_table method is:

            define_table(tablename, *fields, **kwargs)
             
            @@ -907,7 +943,7 @@

            define_table signature

            -

            id: Notes about the primary key

            +

            id: Notes about the primary key

            Do not declare a field called “id”, because one is created by py4web anyway. Every table has a field called “id” by default. It is an auto-increment integer field (usually starting at 1) used for @@ -921,13 +957,13 @@

            idprimarykey parameter.

            -

            plural and singular

            +

            plural and singular

            As pyDAL is a general DAL, it includes plural and singular attributes to refer to the table names so that external elements can use the proper name for a table.

            -

            redefine

            +

            redefine

            Tables can be defined only once but you can force py4web to redefine an existing table:

            db.define_table('person', Field('name'))
            @@ -937,7 +973,7 @@ 

            redefine

            The redefinition may trigger a migration if table definition changes.

            -

            format: Record representation

            +

            format: Record representation

            It is optional but recommended to specify a format representation for records with the format parameter.

            db.define_table('person', Field('name'), format='%(name)s')
            @@ -962,7 +998,7 @@ 

            format

            -

            rname: Real name

            +

            rname: Real name

            rname sets a database backend name for the table. This makes the py4web table name an alias, and rname is the real name used when constructing the query for the backend. To illustrate just one use, @@ -971,17 +1007,17 @@

            rnamername = 'db1.dbo.table1'

            -

            primarykey: Support for legacy tables

            +

            primarykey: Support for legacy tables

            primarykey helps support legacy tables with existing primary keys, even multi-part. See Legacy databases and keyed tables.

            -

            migrate, fake_migrate

            +

            migrate, fake_migrate

            migrate sets migration options for the table. Refer to Migrations for details.

            -

            table_class

            +

            table_class

            If you define your own table class as a sub-class of pydal.objects.Table, you can provide it here; this allows you to extend and override methods. Example:

            @@ -995,7 +1031,7 @@

            table_class

            -

            sequence_name

            +

            sequence_name

            The name of a custom table sequence (if supported by the database). Can create a SEQUENCE (starting at 1 and incrementing by 1) or use this for legacy tables with custom sequences.

            @@ -1005,16 +1041,16 @@

            sequence_name

            -

            trigger_name

            +

            trigger_name

            Relates to sequence_name. Relevant for some backends which do not support auto-increment numeric fields.

            -

            polymodel

            +

            polymodel

            For use with Google App Engine.

            -

            on_define

            +

            on_define

            on_define is a callback triggered when a lazy_table is instantiated, although it is called anyway if the table is not lazy. This allows dynamic changes to the table without losing the advantages of delayed @@ -1038,7 +1074,7 @@

            on_defineon_define.

            -

            Adding attributes to fields and tables

            +

            Adding attributes to fields and tables

            If you need to add custom attributes to fields, you can simply do this: db.table.field.extra = {}

            “extra” is not a keyword; it’s a custom attribute now attached to the @@ -1049,7 +1085,7 @@

            Adding attributes to fields and tables -

            Legacy databases and keyed tables

            +

            Legacy databases and keyed tables

            py4web can connect to legacy databases under some conditions.

            The easiest way is when these conditions are met:

              @@ -1061,7 +1097,7 @@

              Legacy databases and keyed tablesFIeld('...', 'id')).

              +Field('...', 'id')).

              Finally if the legacy table uses a primary key that is not an auto-increment id field it is possible to use a “keyed table”, for example:

              @@ -1094,7 +1130,7 @@

              Legacy databases and keyed tables -

              Field constructor

              +

              Field constructor

              These are the default values of a Field constructor:

              Field(fieldname, type='string', length=None, default=DEFAULT,
                     required=False, requires=DEFAULT,
              @@ -1222,7 +1258,7 @@ 

              Field constructordefault, update, readable, writable, requires.

              -

              Field types and validators

              +

              Field types and validators

              @@ -1321,7 +1357,7 @@

              Field types and validators -

              Run-time field and table modification

              +

              Run-time field and table modification

              Most attributes of fields and tables can be modified after they are defined:

              >>> db.define_table('person', Field('name', default=''), format='%(name)s')
              @@ -1392,7 +1428,7 @@ 

              Run-time field and table modification -

              More on uploads

              +

              More on uploads

              Consider the following model:

              db.define_table('myfile',
                               Field('image', 'upload', default='path/to/file'))
              @@ -1480,7 +1516,7 @@ 

              More on uploads -

              Migrations

              +

              Migrations

              With our example table definition:

              db.define_table('person')
               
              @@ -1545,7 +1581,7 @@

              Migrations -

              Fixing broken migrations

              +

              Fixing broken migrations

              There are two common problems with migrations and there are ways to recover from them.

              One problem is specific with SQLite. SQLite does not enforce column @@ -1589,7 +1625,7 @@

              Fixing broken migrations -

              Migration control summary

              +

              Migration control summary

              The logic of the various migration arguments are summarized in this pseudo-code:

              if DAL.migrate_enabled and table.migrate:
              @@ -1602,9 +1638,9 @@ 

              Migration control summary -

              Table methods

              +

              Table methods

              -

              insert

              +

              insert

              Given a table, you can insert records

              >>> db.person.insert(name="Alex")
               1
              @@ -1643,7 +1679,7 @@ 

              insert

              -

              Query, Set, Rows

              +

              Query, Set, Rows

              Let’s consider again the table defined (and dropped) previously and insert three records:

              >>> db.define_table('person', Field('name'))
              @@ -1682,7 +1718,7 @@ 

              Query

              -

              update_or_insert

              +

              update_or_insert

              Some times you need to perform an insert only if there is no record with the same values as those being inserted. This can be done with

              db.define_table('person',
              @@ -1713,7 +1749,7 @@ 

              update_or_inser

              -

              validate_and_insert, validate_and_update

              +

              validate_and_insert, validate_and_update

              The function

              ret = db.mytable.validate_and_insert(field='value')
               
              @@ -1724,11 +1760,11 @@

              validate_and_in

              except that it calls the validators for the fields before performing the insert and bails out if the validation does not pass. If validation does -not pass the errors can be found in ret.errors. ret.errors holds +not pass the errors can be found in ret["errors"]. ret["errors"] holds a key-value mapping where each key is the field name whose validation failed, and the value of the key is the result from the validation error -(much like form.errors). If it passes, the id of the new record is -in ret.id. Mind that normally validation is done by the form +(much like form["errors"]). If it passes, the id of the new record is +in ret["id"]. Mind that normally validation is done by the form processing logic so this function is rarely needed.

              Similarly

              ret = db(query).validate_and_update(field='value')
              @@ -1740,18 +1776,18 @@ 

              validate_and_in

              except that it calls the validators for the fields before performing the update. Notice that it only works if query involves a single table. The -number of updated records can be found in ret.updated and errors -will be in ret.errors.

              +number of updated records can be found in ret["updated"] and errors +will be in ret["errors"].

              -

              drop

              +

              drop

              Finally, you can drop tables and all data will be lost:

              db.person.drop()
               
              -

              Tagging records

              +

              Tagging records

              Tags allows to add or find properties attached to records in your database.

              -

              It is internally implemented as a table, which in +

              Tags are hierarchical. Then find([“color”]) would return id1 and id2 +because both records have tags with “color”.

              +

              It is internally implemented with the creation of an additional table, which in this example would be db.thing_tags_default, because no tail was -specified on the Tags(table, tail=“default”) constructor.

              -

              The find method is doing a search by startswith of the -parameter. Then find([“color”]) would return id1 and id2 -because both records have tags starting with “color”. py4web uses tags as a -flexible mechanism to manage permissions.

              +specified on the Tags(table, tail=“default”) constructor.

              +

              py4web uses Tags as a flexible mechanism to manage permissions, we’ll see +all the details later on the Authorization using Tags chapter.

              -

              Raw SQL

              +

              Raw SQL

              -

              executesql

              +

              executesql

              The DAL allows you to explicitly issue SQL statements.

              >>> db.executesql('SELECT * FROM person;')
               [(1, u'Massimo'), (2, u'Massimo')]
              @@ -1843,7 +1879,7 @@ 

              executesql

              -

              _lastsql

              +

              _lastsql

              Whether SQL was executed manually using executesql or was SQL generated by the DAL, you can always find the SQL code in db._lastsql. This is useful for debugging purposes:

              @@ -1858,14 +1894,14 @@

              _lastsql

              -

              Timing queries

              +

              Timing queries

              All queries are automatically timed by py4web. The variable db._timings is a list of tuples. Each tuple contains the raw SQL query as passed to the database driver and the time it took to execute in seconds.

              -

              Indexes

              +

              Indexes

              Currently the DAL API does not provide a command to create indexes on tables, but this can be done using the executesql command. This is because the existence of indexes can make migrations complex, and it is @@ -1881,7 +1917,7 @@

              Indexes -

              Generating raw SQL

              +

              Generating raw SQL

              Sometimes you need to generate the SQL but not execute it. This is easy to do with py4web since every command that performs database IO has an equivalent command that does not, and simply returns the SQL that would @@ -1920,7 +1956,7 @@

              Generating raw SQL -

              select command

              +

              select command

              Given a Set, s, you can fetch the records with the command select:

              >>> rows = s.select()
              @@ -2018,7 +2054,7 @@ 

              select

              -

              Using an iterator-based select for lower memory use

              +

              Using an iterator-based select for lower memory use

              Python “iterators” are a type of “lazy-evaluation”. They ‘feed’ data one step at time; traditional Python loops create the entire set of data in memory before looping.

              @@ -2037,7 +2073,7 @@

              Using an iterator-based select for lower memory use

              -

              Rendering rows using represent

              +

              Rendering rows using represent

              You may wish to rewrite rows returned by select to take advantage of formatting information contained in the represents setting of the fields.

              @@ -2065,7 +2101,7 @@

              Rendering rows using represent -

              Shortcuts

              +

              Shortcuts

              The DAL supports various code-simplifying shortcuts. In particular:

              myrecord = db.mytable[id]
               
              @@ -2111,7 +2147,7 @@

              Shortcuts -

              Fetching a Row

              +

              Fetching a Row

              Yet another convenient syntax is the following:

              record = db.mytable(id)
               record = db.mytable(db.mytable.id == id)
              @@ -2125,7 +2161,7 @@ 

              Fetching a Row< record must meet. If they are not met, it also returns None.

              -

              Recursive selects

              +

              Recursive selects

              Consider the previous table person and a new table “thing” referencing a “person”:

              db.define_table('thing',
              @@ -2171,10 +2207,10 @@ 

              Recursive selec a full Query.

              -

              orderby, groupby, limitby, distinct, having, orderby_on_limitby, join, left, cache

              +

              orderby, groupby, limitby, distinct, having, orderby_on_limitby, join, left, cache

              The select command takes a number of optional arguments.

              -

              orderby

              +

              orderby

              You can fetch the records sorted by name:

              >>> for row in db().select(db.person.ALL, orderby=db.person.name):
               ...     print(row.name)
              @@ -2224,7 +2260,7 @@ 

              orderby -

              groupby, having

              +

              groupby, having

              Using groupby together with orderby, you can group records with the same value for the specified field (this is back-end specific, and is not on the Google NoSQL):

              @@ -2247,7 +2283,7 @@

              groupby, having -

              distinct

              +

              distinct

              With the argument distinct=True, you can specify that you only want to select distinct records. This has the same effect as grouping using all specified fields except that it does not require sorting. When using @@ -2273,7 +2309,7 @@

              distinct -

              limitby

              +

              limitby

              With limitby=(min, max), you can select a subset of the records from offset=min to but not including offset=max. In the next example we select the first two records starting at zero:

              @@ -2286,28 +2322,28 @@

              limitby -

              orderby_on_limitby

              +

              orderby_on_limitby

              Note that the DAL defaults to implicitly adding an orderby when using a limitby. This ensures the same query returns the same results each time, important for pagination. But it can cause performance problems. use orderby_on_limitby = False to change this (this defaults to True).

              -

              join, left

              +

              join, left

              These are involved in managing One to many relation. They are described in Inner join and Left outer join sections respectively.

              -

              cache, cacheable

              +

              cache, cacheable

              An example use which gives much faster selects is:

              -
              rows = db(query).select(cache=(cache.ram, 3600), cacheable=True)
              +
              rows = db(query).select(cache=(cache.get, 3600), cacheable=True)
               

              Look at Caching selects, to understand what the trade-offs are.

              -

              Logical operators

              +

              Logical operators

              Queries can be combined using the binary AND operator “&”:

              >>> rows = db((db.person.name=='Alex') & (db.person.id > 3)).select()
               >>> for row in rows: print row.id, row.name
              @@ -2353,7 +2389,7 @@ 

              Logical operators -

              count, isempty, delete, update

              +

              count, isempty, delete, update

              You can count records in a set:

              >>> db(db.person.name != 'William').count()
               3
              @@ -2384,7 +2420,7 @@ 

              countThe update method returns the number of records that were updated.

              -

              Expressions

              +

              Expressions

              The value assigned an update statement can be an expression. For example consider this model

              db.define_table('person',
              @@ -2405,7 +2441,7 @@ 

              Expressions -

              case

              +

              case

              An expression can contain a case clause for example:

              >>> condition = db.person.name.startswith('B')
               >>> yes_or_no = condition.case('Yes', 'No')
              @@ -2419,7 +2455,7 @@ 

              case

              -

              update_record

              +

              update_record

              py4web also allows updating a single record that is already in memory using update_record

              >>> row = db(db.person.id == 2).select().first()
              @@ -2456,7 +2492,7 @@ 

              update_recordTrue.

              -

              Inserting and updating from a dictionary

              +

              Inserting and updating from a dictionary

              A common issue consists of needing to insert or update records in a table where the name of the table, the field to be updated, and the value for the field are all stored in variables. For example: @@ -2474,7 +2510,7 @@

              Inserting and updating from a dictionary -

              first and last

              +

              first and last

              Given a Rows object containing records:

              rows = db(query).select()
               first_row = rows.first()
              @@ -2495,7 +2531,7 @@ 

              first

              -

              as_dict and as_list

              +

              as_dict and as_list

              A Row object can be serialized into a regular dictionary using the as_dict() method and a Rows object can be serialized into a list of dictionaries using the as_list() method. Here are some examples:

              @@ -2514,7 +2550,7 @@

              as_dict<

              -

              Combining rows

              +

              Combining rows

              Rows objects can be combined at the Python level. Here we assume:

              >>> print(rows1)
               person.name
              @@ -2555,7 +2591,7 @@ 

              Combining rows -

              find, exclude, sort

              +

              find, exclude, sort

              Some times you need to perform two selects and one contains a subset of a previous select. In this case it is pointless to access the database again. The find, exclude and sort objects allow you to @@ -2612,10 +2648,12 @@

              findselect method.

              -

              Caching selects

              +

              Caching selects

              The select method also takes a cache argument, which defaults to None. For caching purposes, it should be set to a tuple where the first -element is the cache model (cache.ram, cache.disk, etc.), and +element is the cache function with signature (key, callback, expiration) +(for example cache.get assuming cache +is an instance of the py4web cache object), and the second element is the expiration time in seconds.

              In the following example, you see a controller that caches a select on the previously defined db.log table. The actual select fetches data from @@ -2624,7 +2662,7 @@

              Caching selects
              def cache_db_select():
              -    logs = db().select(db.log.ALL, cache=(cache.ram, 60))
              +    logs = db().select(db.log.ALL, cache=(cache.get, 60))
                   return dict(logs=logs)
               

              @@ -2642,15 +2680,15 @@

              Caching selectscache argument is used in conjunction with cacheable=True the entire Rows object is cached and this results in much faster caching:

              -
              rows = db(query).select(cache=(cache.ram, 3600), cacheable=True)
              +
              rows = db(query).select(cache=(cache.get, 3600), cacheable=True)
               
              -

              Computed and Virtual fields

              +

              Computed and Virtual fields

              -

              Computed fields

              +

              Computed fields

              DAL fields may have a compute attribute. This must be a function (or lambda) that takes a Row object and returns a value for the field. When a new record is modified, including both insertions and updates, if a @@ -2680,7 +2718,7 @@

              Computed fields -

              Virtual fields

              +

              Virtual fields

              Virtual fields are also computed fields (as in the previous subsection) but they differ from those because they are virtual in the sense that they are not stored in the db and they are computed each time records @@ -2689,7 +2727,7 @@

              Virtual fields -

              New style virtual fields (experimental)

              +

              New style virtual fields (experimental)

              py4web provides a new and easier way to define virtual fields and lazy virtual fields. This section is marked experimental because the APIs may still change a little from what is described here.

              @@ -2750,7 +2788,7 @@

              New style virtual fields (experimental) -

              Old style virtual fields

              +

              Old style virtual fields

              In order to define one or more virtual fields, you can also define a container class, instantiate it and link it to a table or to a select. For example, consider the following table:

              @@ -2853,9 +2891,9 @@

              Old style virtual fields -

              Joins and Relations

              +

              Joins and Relations

              -

              One to many relation

              +

              One to many relation

              To illustrate how to implement one to many relations with the DAL, define another table “thing” that refers to the table “person” which we redefine here:

              @@ -2919,7 +2957,7 @@

              One to many relation

              -

              Inner join

              +

              Inner join

              Another way to achieve a similar result is by using a join, specifically an INNER JOIN. py4web performs joins automatically and transparently when the query links two or more tables as in the following example:

              @@ -2975,7 +3013,7 @@

              Inner joinjoin can be list of db.table.on(...) to join.

              -

              Left outer join

              +

              Left outer join

              Notice that Carl did not appear in the list above because he has no things. If you intend to select on persons (whether they have things or not) and their things (if they have any), then you need to perform a @@ -3004,7 +3042,7 @@

              Left outer joindb.mytable.on(...) to the left parameter.

              -

              Grouping and counting

              +

              Grouping and counting

              When doing joins, sometimes you want to group rows according to certain criteria and count them. For example, count the number of things owned by every person. py4web allows this as well. First, you need a count @@ -3031,7 +3069,7 @@

              Grouping and counting

              -

              Many to many relation

              +

              Many to many relation

              In the previous examples, we allowed a thing to have one owner but one person could have many things. What if Boat was owned by Alex and Curt? This requires a many-to-many relation, and it is realized via an @@ -3100,13 +3138,12 @@

              Many to many relationCurt

              -

              A lighter alternative to many-to-many relations is tagging, you can -found an example of this in the next section. Tagging works even on -database backends that do not support JOINs like the Google App Engine -NoSQL.

              +

              A lighter alternative to many-to-many relations is tagging, see the +Authorization using Tags chapter. Tagging works even on database backends +that do not support JOINs like the Google App Engine NoSQL.

              -

              Self-Reference and aliases

              +

              Self-Reference and aliases

              It is possible to define tables with fields that refer to themselves, here is an example:

              db.define_table('person',
              @@ -3187,7 +3224,7 @@ 

              Self-Reference and aliases -

              Other operators

              +

              Other operators

              py4web has other operators that provide an API to access equivalent SQL operators. Let’s define another table “log” to store security events, their event_time and severity, where the severity is an integer number.

              @@ -3212,7 +3249,7 @@

              Other operators -

              like, ilike, regexp, startswith, endswith, contains, upper, lower

              +

              like, ilike, regexp, startswith, endswith, contains, upper, lower

              Fields have a like operator that you can use to match strings:

              >>> for row in db(db.log.event.like('port%')).select():
               ...     print(row.event)
              @@ -3273,7 +3310,7 @@ 

              like

              -

              year, month, day, hour, minutes, seconds

              +

              year, month, day, hour, minutes, seconds

              The date and datetime fields have day, month and year methods. The datetime and time fields have hour, minutes and seconds methods. Here is an example:

              @@ -3287,7 +3324,7 @@

              year

              -

              belongs

              +

              belongs

              The SQL IN operator is realized via the belongs method which returns true when the field value belongs to the specified set (list or tuples):

              >>> for row in db(db.log.severity.belongs((1, 2))).select():
              @@ -3334,7 +3371,7 @@ 

              belongs< person “Jonathan”. The two lines result in one single SQL query.

              -

              sum, avg, min, max and len

              +

              sum, avg, min, max and len

              Previously, you have used the count operator to count records. Similarly, you can use the sum operator to add (sum) the values of a specific field from a group of records. As in the case of count, the @@ -3370,7 +3407,7 @@

              sum

              -

              Substrings

              +

              Substrings

              One can build an expression to refer to a substring. For example, we can group things whose name starts with the same three characters and select only one from each group:

              @@ -3379,7 +3416,7 @@

              Substrings -

              Default values with coalesce and coalesce_zero

              +

              Default values with coalesce and coalesce_zero

              There are times when you need to pull a value from database but also need a default values if the value for a record is set to NULL. In SQL there is a function, COALESCE, for this. py4web has an equivalent @@ -3420,9 +3457,9 @@

              Default values with -

              Exporting and importing data

              +

              Exporting and importing data

              -

              CSV (one Table at a time)

              +

              CSV (one Table at a time)

              When a Rows object is converted to a string it is automatically serialized in CSV:

              >>> rows = db(db.person.id == db.thing.owner_id).select()
              @@ -3474,7 +3511,7 @@ 

              CSV (one Table at a time) -

              CSV (all tables at once)

              +

              CSV (all tables at once)

              In py4web, you can backup/restore an entire database with two commands:

              To export:

              with open('somefile.csv', 'w', encoding='utf-8', newline='') as dumpfile:
              @@ -3512,7 +3549,7 @@ 

              CSV (all tables at once)

              -

              CSV and remote database synchronization

              +

              CSV and remote database synchronization

              Consider once again the following model:

              db.define_table('person',
                               Field('name'))
              @@ -3610,7 +3647,7 @@ 

              CSV and remote database synchronization -

              HTML and XML (one Table at a time)

              +

              HTML and XML (one Table at a time)

              Rows objects also have an xml method (like helpers) that serializes it to XML/HTML:

              >>> rows = db(db.person.id == db.thing.owner_id).select()
              @@ -3646,7 +3683,7 @@ 

              HTML and XML (one Table at a time)TAG helper +tags, you can easily do that using the universal TAG XML helper that we’ll see later and the Python syntax *<iterable> allowed in function calls:

              >>> rows = db(db.person).select()
              @@ -3660,9 +3697,15 @@ 

              HTML and XML (one Table at a time)</result>

              +
              +

              Warning

              +

              Do not confuse the TAG XML helper used here (see the TAG +chapter) with the Tags method that will be extensively explained +on the Authorization using Tags chapter.

              +

              -

              Data representation

              +

              Data representation

              The Rows.export_to_csv_file method accepts a keyword argument named represent. When True it will use the columns represent function while exporting the data instead of the raw data.

              @@ -3693,9 +3736,9 @@

              Data representation -

              Advanced features

              +

              Advanced features

              -

              list:<type> and contains

              +

              list:<type> and contains

              py4web provides the following special field types:

              list:string
               list:integer
              @@ -3779,7 +3822,7 @@ 

              Advanced features -

              Table inheritance

              +

              Table inheritance

              It is possible to create a table that contains all the fields from another table. It is sufficient to pass the other table in place of a field to define_table. For example

              @@ -3815,7 +3858,7 @@

              Table inheritance -

              filter_in and filter_out

              +

              filter_in and filter_out

              It is possible to define a filter for each field to be called before a value is inserted into the database for that field and after a value is retrieved from the database.

              @@ -3840,7 +3883,7 @@

              Table inheritanceSQLCustomType, as discussed in Custom Field types.

              -

              callbacks on record insert, delete and update

              +

              callbacks on record insert, delete and update

              PY4WEB provides a mechanism to register callbacks to be called before and/or after insert, update and delete of records.

              Each table stores six lists of callbacks:

              @@ -3898,7 +3941,7 @@

              callbacks on record insert, delete and updateupdate but ignores before and after callbacks.

              -

              Database cascades

              +

              Database cascades

              Database schema can define relationships which trigger deletions of related records, known as cascading. The DAL is not informed when a record is deleted due to a cascade. So no *_delete callback will ever @@ -3906,7 +3949,7 @@

              Database cascades -

              Record versioning

              +

              Record versioning

              It is possible to ask py4web to save every copy of a record when the record is individually modified. There are different ways to do it and it can be done for all tables at once using the syntax:

              @@ -3957,7 +4000,7 @@

              Record versioning -

              Common filters

              +

              Common filters

              A common filter is a generalization of the above multi-tenancy idea. It provides an easy way to prevent repeating of the same query. Consider for example the following table:

              @@ -3992,7 +4035,7 @@

              Common filters -

              Custom Field types

              +

              Custom Field types

              Aside for using filter_in and filter_out, it is possible to define new/custom field types. For example, suppose that you want to define a custom type to store an IP address:

              @@ -4050,7 +4093,7 @@

              Common filters -

              Using DAL without define tables

              +

              Using DAL without define tables

              The DAL can be used from any Python program simply by doing this:

              from pydal import DAL, Field
               db = DAL('sqlite://storage.sqlite', folder='path/to/app/databases')
              @@ -4070,7 +4113,7 @@ 

              Using DAL without define tables -

              Distributed transaction

              +

              Distributed transaction

              At the time of writing this feature is only supported by PostgreSQL, MySQL and Firebird, since they expose API for two-phase commits.

              @@ -4093,7 +4136,7 @@

              Distributed transaction

              -

              Copy data from one db into another

              +

              Copy data from one db into another

              Consider the situation in which you have been using the following database:

              db = DAL('sqlite://storage.sqlite')
              @@ -4110,9 +4153,9 @@ 

              Copy data from one db into another -

              Gotchas

              +

              Gotchas

              -

              Note on new DAL and adapters

              +

              Note on new DAL and adapters

              The source code of the Database Abstraction Layer was completely rewritten in 2010. While it stays backward compatible, the rewrite made it more modular and easier to extend. Here we explain the main logic.

              @@ -4386,7 +4429,7 @@

              Note on new DAL and adapters -

              SQLite

              +

              SQLite

              SQLite does not support dropping and altering columns. That means that py4web migrations will work up to a point. If you delete a field from a table, the column will remain in the database but will be invisible to @@ -4405,7 +4448,7 @@

              SQLite

              -

              MySQL

              +

              MySQL

              MySQL does not support multiple ALTER TABLE within a single transaction. This means that any migration process is broken into multiple commits. If something happens that causes a failure it is possible to break a @@ -4418,7 +4461,7 @@

              MySQL again).

              -

              Google SQL

              +

              Google SQL

              Google SQL has the same problems as MySQL and more. In particular table metadata itself must be stored in the database in a table that is not migrated by py4web. This is because Google App Engine has a read-only @@ -4431,7 +4474,7 @@

              Google SQLpy4web_filesystem.

              -

              MSSQL (Microsoft SQL Server)

              +

              MSSQL (Microsoft SQL Server)

              MSSQL < 2012 does not support the SQL OFFSET keyword. Therefore the database cannot do pagination. When doing a limitby=(a, b) py4web will fetch the first a + b rows and discard the first a. This @@ -4468,7 +4511,7 @@

              MSSQL (Microsoft SQL Server) -

              Oracle

              +

              Oracle

              Oracle also does not support pagination. It does not support neither the OFFSET nor the LIMIT keywords. PY4WEB achieves pagination by translating a db(...).select(limitby=(a, b)) into a complex three-way nested @@ -4477,7 +4520,7 @@

              Oracle

              -

              Google NoSQL (Datastore)

              +

              Google NoSQL (Datastore)

              Google NoSQL (Datastore) does not allow joins, left joins, aggregates, expression, OR involving more than one table, the ‘like’ operator searches in “text” fields.

              @@ -4507,7 +4550,7 @@

              Google NoSQL (Datastore)
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Built with
              Sphinx using a @@ -4532,7 +4575,7 @@

              Google NoSQL (Datastore) - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/en/chapter-08.html b/apps/_documentation/static/en/chapter-08.html index 43ad70afa..1faf0618e 100644 --- a/apps/_documentation/static/en/chapter-08.html +++ b/apps/_documentation/static/en/chapter-08.html @@ -1,25 +1,27 @@ - + - + - The RestAPI — py4web 1.20230507.1 documentation - - - - + The RestAPI — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
              - 1.20230507.1 + 20240915
              @@ -56,7 +58,7 @@
            • Help, resources and hints
            • Installation and Startup
            • The Dashboard
            • -
            • Creating your first app
            • +
            • Creating an app
            • Fixtures
            • The Database Abstraction Layer (DAL)
            • The RestAPI @@ -181,13 +184,64 @@
              -

              Forms

              +

              Forms

              The Form class provides a high-level API for quickly building CRUD (create, update and delete) forms, especially for working on an existing database table. It can generate and process a form from a -list of desired fields and/or from an existing database table. -It is a pretty much equivalent to web2py’s SQLFORM.

              +list of desired fields and/or from an existing database table.

              +

              There are 3 types of forms:

              +

              CRUD Create forms:

              +
              @action('create_thing')
              +@action.uses('generic.html', db, flash)
              +def create_thing():
              +    form = Form(db.thing)
              +    if form.accepted:
              +        flash.set("record created")
              +        redirect(URL('other_page'))
              +    return locals()
              +
              +
              +

              CRUD Update forms:

              +
              @action('update_thing/<thing_id:int>')
              +@action.uses('generic.html', db, flash)
              +def update_thing(thing_id):
              +    form = Form(db.thing, thing_id)
              +    if form.accepted:
              +        flash.set("record updated")
              +        redirect(URL('other_page'))
              +    return locals()
              +
              +
              +

              Non-CRUD forms (not associated to a database):

              +
              @action('some_form')
              +@action.uses('generic.html', flash)
              +def some_form():
              +    fields = [
              +        Field("name", requires=IS_NOT_EMPTY()),
              +        Field("color", required=IS_IN_SET(["red","blue","green"])),
              +    ]
              +    form = Form(fields)
              +    if form.accepted:
              +        flash.set("information recorded")
              +        redirect(URL('other_page'))
              +    return locals()
              +
              +
              +

              The use of flash is optional. flash is defined in common.py +in the scaffolding application. It simply stores a message in a cookie +so it can be recovered and displayed after redirection. +This is done in the default layout.

              +

              In this chapter from now on we will assume the following model and +an app derived from the scaffolding app:

              +
              db.define_table(
              +    'thing',
              +    Field('name', requires=IS_NOT_EMPTY()),
              +    Field('color', requires=IS_IN_SET(['red','blue','green'])),
              +    Field('image', 'upload', download_url=lambda name: URL('download', name)),
              +)
              +
              +
              -

              The Form constructor

              +

              The Form constructor

              The Form constructor accepts the following arguments:

              Form(self,
                    table,
              @@ -226,43 +280,39 @@ 

              The Form constructor

              -

              A minimal form example without a database

              +

              A minimal form example without a database

              Let’s start with a minimal working form example. Create a new minimal app called form_minimal :

              -
              # in form_minimal/__init__.py
              -from py4web import action, Field, redirect, URL
              +
              # in controllers.py
              +from py4web impot action, redirect, URL, Field
               from py4web.utils.form import Form
              -from pydal.validators import IS_NOT_EMPTY
              -
              +from pydal.validators import *
               
               @action('index', method=['GET', 'POST'])
               @action.uses('form_minimal.html')
               def index():
              -    form = Form([
              -        Field('product_name'),
              -        Field('product_quantity', 'integer', requires=IS_NOT_EMPTY()),
              -        ])
              +    fields = [
              +        Field('name', requires=IS_NOT_EMPTY()),
              +        Field('color', requires=IS_IN_SET(['red','blue','green'])),
              +    ]
              +    form = Form(fields)
                   if form.accepted:
              -        # Do something with form.vars['product_name'] and form.vars['product_quantity']
              +        # Do something with form.vars['name'] and form.vars['color']
                       redirect(URL('accepted'))
                   if form.errors:
              -        # display message error
              -        redirect(URL('not_accepted'))
              +        # do something
              +        ...
                   return dict(form=form)
               
               @action("accepted")
               def accepted():
                   return "form_example accepted"
              -
              -
              -@action("not_accepted")
              -def not_accepted():
              -    return "form_example NOT accepted"
               

              Also, you need to create a file inside the app called templates/form_minimal.html that just contains the line:

              -
              [[=form]]
              +
              [[extend 'layout.html']]
              +[[=form]]
               

              Then reload py4web and visit http://127.0.0.1:8000/form_minimal - you’ll get the Form page:

              @@ -279,44 +329,32 @@

              A minimal form example without a database -

              Basic form example

              -

              In this next basic example we generate a form from a database. +

              Basic form example

              +

              In this next basic example we generate a CRUD create form from a database. Create a new minimal app called form_basic :

              -
              # in form_basic/__init__.py
              -import os
              -from py4web import action, Field, DAL
              -from py4web.utils.form import Form, FormStyleDefault
              -from pydal.validators import IS_NOT_EMPTY, IS_IN_SET
              -
              -# database definition
              -DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
              -if not os.path.isdir(DB_FOLDER):
              -    os.mkdir(DB_FOLDER)
              -db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER)
              -db.define_table(
              -    'person',
              -    Field('superhero', requires=IS_NOT_EMPTY()),
              -    Field('realname'),
              -    Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])),
              -)
              +
              # in controllers.py
              +from py4web import action, redirect, URL, Field
              +from py4web.utils.form import Form
              +from pydal.validators import *
              +from .common import db
               
               # controllers definition
              -@action("index", method=["GET", "POST"])
              +@action("create_form", method=["GET", "POST"])
               @action.uses("form_basic.html", db)
              -def index(id=None):
              -    form = Form(db.person, id, deletable=False, formstyle=FormStyleDefault)
              -    rows = db(db.person).select()
              +def create_form():
              +    form = Form(db.thing)
              +    rows = db(db.thing).select()
                   return dict(form=form, rows=rows)
               
              -

              Because this is a dual purpose form, in case an id is passed, we also validate it -by checking if the corresponding record exists and raise 404 if not.

              Note the import of two simple validators on top, in order to be used later with the requires parameter. We’ll fully explain them on the Form validation paragraph.

              You will also need a template file templates/form_basic.html that contains, for example, the following code:

              -
              <h2 class="title">Form Basic example: Superhero Identity</h2>
              +
              [[extend "layout.html"]]
              +
              +<h2 class="title">Form Basic example: My Things</h2>
               
               [[=form]]
               
              @@ -324,56 +362,46 @@ 

              Basic form example<ul> [[for row in rows:]] -<li>[[=row.id]]: [[=row.superhero]] ([[=row.realname]]) from [[=row.universe]]</li> +<li>[[=row.id]]: [[=row.name]] has color [[=row.color]]</li> [[pass]] </ul>

              -

              Reload py4web and visit http://127.0.0.1:8000/form_basic : +

              Reload py4web and visit http://127.0.0.1:8000/create_form : the result is an input form on the top of the page, and the list of all the previously added entries on the bottom:

              _images/form2.png

              This is a simple example and you cannot change nor delete existing records. But if you’d like to experiment, the database content can be fully seen and changed with the Dashboard app.

              -

              Notice that py4web by default let you choose the value of the universe field using -a dropdown menu:

              -_images/form3.png -

              The basic form usage is quite useful for rapid prototyping of programs, since you don’t need -to specify the layout of the form. On the other hand, you cannot change its default behaviour.

              +

              You can turn a create form into a CRUD update form by passing a record or a record id +it second argument:

              +
              # controllers definition
              +@action("update_form/<thing_id:int>", method=["GET", "POST"])
              +@action.uses("form_basic.html", db)
              +def update_form():
              +    form = Form(db.thing, thing_id)
              +    rows = db(db.thing).select()
              +    return dict(form=form, rows=rows)
              +
              +
              -

              File upload field

              -

              The file upload field is quite particular. The standard way to use it (as in the _scaffold app) -is to have the UPLOAD_FOLDER defined in the common.py file. But if you don’t specify it, then the -default value of your_app/upload folder will be used (and the folder will also be created if needed). -Let’s look at a simple example:

              -
              # in form_upload/__init__.py
              -import os
              -from py4web.core import required_folder
              -from py4web import action, Field, DAL
              -from py4web.utils.form import Form, FormStyleDefault
              -from pydal.validators import IS_NOT_EMPTY
              -
              -# database definition
              -DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
              -if not os.path.isdir(DB_FOLDER):
              -    os.mkdir(DB_FOLDER)
              -db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER)
              -db.define_table(
              -    'person',
              -    Field('superhero', requires=IS_NOT_EMPTY()),
              -    Field('image', "upload", label='Superhero Image', requires=IS_NOT_EMPTY()),
              +

              File upload field

              +

              We can make a minor modification to our reference model and an upload type file:

              +
              db.define_table(
              +    'thing',
              +    Field('name', requires=IS_NOT_EMPTY()),
              +    Field('color', requires=IS_IN_SET(['red','blue','green'])),
              +    Field('image', 'upload', download_url=lambda image: URL('download', image)),
               )
              -
              -@action("index", method=["GET", "POST"])
              -@action.uses("form_upload.html", db)
              -def upload(id=None):
              -    form = Form(db.person, id, deletable=False, formstyle=FormStyleDefault)
              -    rows = db(db.person).select()
              -    return dict(form=form, rows=rows)
               
              -

              And in templates/form_upload.html :

              -
              <h2 class="title">Form upload example: Superhero Identity</h2>
              +

              The file upload field is quite particular. The standard way to use it (as in the _scaffold app) +is to have the UPLOAD_FOLDER defined in the common.py file. But if you don’t specify it, then the +default value of your_app/upload folder will be used (and the folder will also be created if needed). +download_url is a callback that given the image name, generated the URL to download. The download +url is predefined in common.py.

              +

              We can modify form_basic.html to display the uploaded images:

              + -

              This gives a result like the following:

              -_images/form6.png -

              Note that the uploaded files will be saved on the UPLOAD_FOLDER folder with their name hashed. +

              The uploaded files (the thing images) are saved on the UPLOAD_FOLDER folder with their name hashed. Other details on the upload fields can be found on Field constructor paragraph, including a way to save the files inside the database itself.

              -

              Widgets

              +

              Widgets

              -

              Standard widgets

              +

              Standard widgets

              Py4web provides many widgets in the py4web.utility.form library. They are simple plugins that easily allow you to specify the type of the input elements in a form, along with some of their properties.

              @@ -412,31 +439,19 @@

              Standard widgets
              # in form_widgets/__init__.py
              -import os
              -from py4web import action, Field, DAL
              +
              # in controllers.py
              +from py4web import action, redirect, URL, Field
               from py4web.utils.form import Form, FormStyleDefault, RadioWidget
              -from pydal.validators import IS_NOT_EMPTY, IS_IN_SET
              -
              -# database definition
              -DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
              -if not os.path.isdir(DB_FOLDER):
              -    os.mkdir(DB_FOLDER)
              -db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER)
              -db.define_table(
              -    'person',
              -    Field('superhero', requires=IS_NOT_EMPTY()),
              -    Field('realname'),
              -    Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])),
              -)
              +from pydal.validators import *
              +from .common import db
               
               # controllers definition
              -@action("index", method=["GET", "POST"])
              +@action("create_form", method=["GET", "POST"])
               @action.uses("form_widgets.html", db)
              -def index(id=None):
              -    FormStyleDefault.widgets['universe']=RadioWidget()
              -    form = Form(db.person, id, deletable=False, formstyle=FormStyleDefault)
              -    rows = db(db.person).select()
              +def create_form():
              +    FormStyleDefault.widgets['color']=RadioWidget()
              +    form = Form(db.thing, formstyle=FormStyleDefault)
              +    rows = db(db.thing).select()
                   return dict(form=form, rows=rows)
               
              @@ -444,58 +459,27 @@

              Standard widgetsuniverse field form style with the line:

              +
            • before the form definition, you define the color field form style with the line:

              -
              FormStyleDefault.widgets['universe']=RadioWidget()
              +
              FormStyleDefault.widgets['color']=RadioWidget()
               
            • -

              You will also need a template file templates/form_widgets.html that -contains the following code (as the form_basic.html) :

              -
              <h2 class="title">Form Widget example: Superhero Identity</h2>
              -
              -[[=form]]
              -
              -<h2 class="title">Rows</h2>
              -
              -<ul>
              -[[for row in rows:]]
              -<li>[[=row.id]]: [[=row.superhero]] ([[=row.realname]]) from [[=row.universe]]</li>
              -[[pass]]
              -</ul>
              -
              -

              The result is the same as before, but now we have a radio button widget instead of the dropdown menu!

              -_images/form4.png

              Using widgets in forms is quite easy, and they’ll let you have more control on its pieces.

              -

              Custom widgets

              +

              Custom widgets

              You can also customize the widgets properties by subclassing the FormStyleDefault class. Let’s have a quick look, improving again our Superhero example:

              -
              #
              -# in form_custom_widgets/__init__.py
              -#
              -import os
              -from py4web import action, Field, DAL
              +
              # in controllers.py
              +from py4web import action, redirect, URL, Field
               from py4web.utils.form import Form, FormStyleDefault, RadioWidget
              -from pydal.validators import IS_NOT_EMPTY, IS_IN_SET
              -from yatl.helpers import INPUT, DIV
              -
              -# database definition
              -DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
              -if not os.path.isdir(DB_FOLDER):
              -    os.mkdir(DB_FOLDER)
              -db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER)
              -db.define_table(
              -    'person',
              -    Field('superhero', requires=IS_NOT_EMPTY()),
              -    Field('realname'),
              -    Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])),
              -)
              +from pydal.validators import *
              +from .common import db
               
               # custom widget class definition
               class MyCustomWidget:
              @@ -514,45 +498,28 @@ 

              Custom widgetsreturn control # controllers definition -@action("index", method=["GET", "POST"]) +@action("create_form", method=["GET", "POST"]) @action.uses("form_custom_widgets.html", db) -def index(id=None): +def create_form(): MyStyle = FormStyleDefault MyStyle.classes = FormStyleDefault.classes - MyStyle.widgets['superhero']=MyCustomWidget() - MyStyle.widgets['realname']=MyCustomWidget() - MyStyle.widgets['universe']=RadioWidget() + MyStyle.widgets['name']=MyCustomWidget() + MyStyle.widgets['color']=RadioWidget() - form = Form(db.person, id, deletable=False, formstyle=MyStyle) - rows = db(db.person).select() + form = Form(db.thing, deletable=False, formstyle=MyStyle) + rows = db(db.thing).select() return dict(form=form, rows=rows)

              -

              You will also need a template file templates/form_custom_widgets.html that -contains the following code (as the form_basic.html) :

              -
              <h2 class="title">Form Custom Widgets example: Superhero Identity</h2>
              -
              -[[=form]]
              -
              -<h2 class="title">Rows</h2>
              -
              -<ul>
              -[[for row in rows:]]
              -<li>[[=row.id]]: [[=row.superhero]]  ([[=row.realname]]) from [[=row.universe]] </li>
              -[[pass]]
              -</ul>
              -
              -

              The result is similar to the previous ones, but now we have a custom input field, -with foreground color red and background color black:

              -_images/form5.png +with foreground color red and background color black,

              Even the radio button widget has changed, from red to blue.

              -

              Advanced form design

              +

              Advanced form design

              -

              Form structure manipulation

              +

              Form structure manipulation

              In py4web a form is rendered by YATL helpers. This means the tree structure of a form can be manipulated before the form is serialized in HTML. Here is an example of how to manipulate the generate HTML structure:

              @@ -567,7 +534,7 @@

              Form structure manipulation -

              Custom forms

              +

              Custom forms

              Custom forms allow you to granulary control how the form is processed. In the template file, you can execute specific instructions before the form is displayed or after its data submission by inserting code among the following statements:

              -

              The sidecar parameter

              +

              The sidecar parameter

              The sidecar is the stuff injected in the form along with the submit button.

              For example, you can inject a simple click me button in your form with the following code:

              @@ -621,7 +611,7 @@

              The sidecar parameter

              -

              Form validation

              +

              Form validation

              Validators are classes used to validate input fields (including forms generated from database tables). They are normally assigned using the requires attribute of a table Field object, as already shown on the Field constructor paragraph of the DAL chapter. Also, you can use advanced validators @@ -710,37 +700,37 @@

              Form validation -

              Text format validators

              +

              Text format validators

              -

              IS_ALPHANUMERIC

              +

              IS_ALPHANUMERIC

              This validator checks that a field value contains only characters in the ranges a-z, A-Z, 0-9, and underscores.

              requires = IS_ALPHANUMERIC(error_message='must be alphanumeric!')
               
              -

              IS_LOWER

              +

              IS_LOWER

              This validator never returns an error. It just converts the value to lower case.

              requires = IS_LOWER()
               
              -

              IS_UPPER

              +

              IS_UPPER

              This validator never returns an error. It converts the value to upper case.

              requires = IS_UPPER()
               
              -

              IS_EMAIL

              +

              IS_EMAIL

              It checks that the field value looks like an email address. It does not try to send email to confirm.

              requires = IS_EMAIL(error_message='invalid email!')
               
              -

              IS_MATCH

              +

              IS_MATCH

              This validator matches the value against a regular expression and returns an error if it does not match. Here is an example of usage to validate a US zip code:

              requires = IS_MATCH('^\d{5}(-\d{4})?$',
              @@ -770,7 +760,7 @@ 

              IS_MATCH substring rather than the original value.

              -

              IS_LENGTH

              +

              IS_LENGTH

              Checks if length of field’s value fits between given boundaries. Works for both text and file inputs.

              Its arguments are:

              @@ -799,7 +789,7 @@

              IS_LENGTH

              -

              IS_URL

              +

              IS_URL

              Rejects a URL string if any of the following is true:

              • The string is empty or None

              • @@ -844,8 +834,19 @@

                IS_URL

            • +
              +

              IS_SAFE

              +
              requires = IS_SAFE(error_message='Unsafe Content')
              +requires = IS_SAFE(mode="sanitize")
              +requires = IS_SAFE(sanitizer=lambda text: str(XML(text, sanitize=True)))
              +
              +
              +

              This validators is for text fields that should contain HTML and may contain invalid tags (script, ember, object, iframe). +It works by trying to sanitize the content and either provide an error (mode=”error”) or replacing the content +with the sanitized one (mode=”sanitize”). You can specify the error message, the mode, and provide your own sanitizer.

              +
              -

              IS_SLUG

              +

              IS_SLUG

              requires = IS_SLUG(maxlen=80, check=False, error_message='must be slug')
               
              @@ -853,7 +854,7 @@

              IS_SLUG<

              If check is set to False (default) it converts the input value to a slug.

              -

              IS_JSON

              +

              IS_JSON

              requires = IS_JSON(error_message='Invalid json', native_json=False)
               
              @@ -862,16 +863,16 @@

              IS_JSON<

              -

              Date and time validators

              +

              Date and time validators

              -

              IS_TIME

              +

              IS_TIME

              This validator checks that a field value contains a valid time in the specified format.

              requires = IS_TIME(error_message='must be HH:MM:SS!')
               
              -

              IS_DATE

              +

              IS_DATE

              This validator checks that a field value contains a valid date in the specified format. It is good practice to specify the format using the translation operator, in order to support different formats in different locales.

              requires = IS_DATE(format=T('%Y-%m-%d'),
                   error_message='must be YYYY-MM-DD!')
              @@ -880,7 +881,7 @@ 

              IS_DATE<

              For the full description on % directives look under the IS_DATETIME validator.

              -

              IS_DATETIME

              +

              IS_DATETIME

              This validator checks that a field value contains a valid datetime in the specified format. It is good practice to specify the format using the translation operator, in order to support different formats in different locales.

              requires = IS_DATETIME(format=T('%Y-%m-%d %H:%M:%S'),
                                  error_message='must be YYYY-MM-DD HH:MM:SS!')
              @@ -902,7 +903,7 @@ 

              IS_DATETIME

              -

              IS_DATE_IN_RANGE

              +

              IS_DATE_IN_RANGE

              Works very much like the previous validator but allows to specify a range:

              requires = IS_DATE_IN_RANGE(format=T('%Y-%m-%d'),
                               minimum=datetime.date(2008, 1, 1),
              @@ -913,7 +914,7 @@ 

              IS_DATE_IN_RANG

              For the full description on % directives look under the IS_DATETIME validator.

              -

              IS_DATETIME_IN_RANGE

              +

              IS_DATETIME_IN_RANGE

              Works very much like the previous validator but allows to specify a range:

              requires = IS_DATETIME_IN_RANGE(format=T('%Y-%m-%d %H:%M:%S'),
                                   minimum=datetime.datetime(2008, 1, 1, 10, 30),
              @@ -925,9 +926,9 @@ 

              IS_DATETIME_IN_

              -

              Range, set and equality validators

              +

              Range, set and equality validators

              -

              IS_EQUAL_TO

              +

              IS_EQUAL_TO

              Checks whether the validated value is equal to a given value (which can be a variable):

              requires = IS_EQUAL_TO(request.vars.password,
                                   error_message='passwords do not match')
              @@ -935,7 +936,7 @@ 

              IS_EQUAL_TO

              -

              IS_NOT_EMPTY

              +

              IS_NOT_EMPTY

              This validator checks that the content of the field value is neither None nor an empty string nor an empty list. A string value is checked for after a .strip().

              requires = IS_NOT_EMPTY(error_message='cannot be empty!')
               
              @@ -946,11 +947,11 @@

              IS_NOT_EMPTY

              -

              IS_NULL_OR

              +

              IS_NULL_OR

              Deprecated, an alias for IS_EMPTY_OR described below.

              -

              IS_EMPTY_OR

              +

              IS_EMPTY_OR

              Sometimes you need to allow empty values on a field along with other requirements. For example a field may be a date but it can also be empty. The IS_EMPTY_OR validator allows this:

              requires = IS_EMPTY_OR(IS_DATE())
              @@ -964,7 +965,7 @@ 

              IS_EMPTY_OR

              -

              IS_EXPR

              +

              IS_EXPR

              This validator let you express a general condition by means of a callable which takes a value to validate and returns the error message or None to accept the input value.

              requires = IS_EXPR(lambda v: T('not divisible by 3') if int(v) % 3 else None)
               
              @@ -982,7 +983,7 @@

              IS_EXPR<

              -

              IS_DECIMAL_IN_RANGE

              +

              IS_DECIMAL_IN_RANGE

              INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10, dot="."))
               
              @@ -994,7 +995,7 @@

              IS_DECIMAL_IN_R

              The dot argument is optional and allows you to internationalize the symbol used to separate the decimals.

              -

              IS_FLOAT_IN_RANGE

              +

              IS_FLOAT_IN_RANGE

              Checks that the field value is a floating point number within a definite range, 0 <= value <= 100 in the following example:

              requires = IS_FLOAT_IN_RANGE(0, 100, dot=".",
                                           error_message='negative or too large!')
              @@ -1003,7 +1004,7 @@ 

              IS_FLOAT_IN_RAN

              The dot argument is optional and allows you to internationalize the symbol used to separate the decimals.

              -

              IS_INT_IN_RANGE

              +

              IS_INT_IN_RANGE

              Checks that the field value is an integer number within a definite range,

              0 <= value < 100 in the following example:

              requires = IS_INT_IN_RANGE(0, 100,
              @@ -1014,7 +1015,7 @@ 

              IS_INT_IN_RANGE

              -

              IS_IN_SET

              +

              IS_IN_SET

              This validator will automatically set the form field to an option field (ie, with a drop-down menu).

              IS_IN_SET checks that the field values are in a set:

              requires = IS_IN_SET(['a', 'b', 'c'], zero=T('choose one'),
              @@ -1032,14 +1033,14 @@ 

              IS_IN_SET

              -

              Checkbox validation

              +

              Checkbox validation

              To force a filled-in form checkbox (such as an acceptance of terms and conditions), use this:

              -
              requires=IS_IN_SET(['on'])
              +
              requires=IS_IN_SET(['ON'])
               
              -

              Dictionaries and tuples with IS_IN_SET

              +

              Dictionaries and tuples with IS_IN_SET

              You may also use a dictionary or a list of tuples to make the drop down list more descriptive:

              # Dictionary example:
               requires = IS_IN_SET({'A':'Apple', 'B':'Banana', 'C':'Cherry'}, zero=None)
              @@ -1050,16 +1051,16 @@ 

              Dictionaries and tuples with IS_IN_SET -

              Sorted options

              +

              Sorted options

              To keep the options alphabetically sorted by their labels into the drop down list, use the sort argument with IS_IN_SET.

              IS_IN_SET([('H', 'Hulk'), ('S', 'Superman'), ('B', 'Batman')], sort=True)
               
              -

              IS_IN_SET and Tagging

              +

              IS_IN_SET and Tagging

              The IS_IN_SET validator has an optional attribute multiple=False. If set to True, multiple values can be stored in one -field. The field should be of type list:integer or list:string as discussed in [[Chapter 6 ../06#list-type-and-contains]]. +field. The field should be of type list:integer or list:string as discussed in list:<type> and contains. An explicit example of tagging is discussed there. We strongly suggest using the jQuery multiselect plugin to render multiple fields.

              Note that when multiple=True, IS_IN_SET will accept zero or more values, i.e. it will accept the field when nothing has been selected. multiple can also be a tuple of the form (a, b) where a and b are the minimum and (exclusive) maximum number of items @@ -1067,9 +1068,9 @@

              IS_IN_SET

              -

              Complexity and security validators

              +

              Complexity and security validators

              -

              IS_STRONG

              +

              IS_STRONG

              Enforces complexity requirements on a field (usually a password field).

              Example:

              requires = IS_STRONG(min=10, special=2, upper=2)
              @@ -1099,7 +1100,7 @@ 

              IS_STRONGnumber = 1, special = 1 which otherwise are all sets to None.

              -

              CRYPT

              +

              CRYPT

              This is also a filter. It performs a secure hash on the input and it is used to prevent passwords from being passed in the clear to the database.

              requires = CRYPT()
               
              @@ -1131,9 +1132,9 @@

              CRYPT

              -

              Special type validators

              +

              Special type validators

              -

              IS_LIST_OF

              +

              IS_LIST_OF

              This validator helps you to ensure length limits on values of type list, for this purpose use its minimum, maximum, and error_message arguments, for example:

              requires = IS_LIST_OF(minimum=2)
              @@ -1152,7 +1153,7 @@ 

              IS_LIST_OF

              -

              IS_LIST_OF_EMAILS

              +

              IS_LIST_OF_EMAILS

              This validator is specifically designed to work with the following field:

              Field('emails', 'list:string',
                     widget=SQLFORM.widgets.text.widget,
              @@ -1178,7 +1179,7 @@ 

              IS_LIST_OF_EMAI

              The effect of the represent argument (at lines 6 and 7) is to add a “mailto:…” link to each email address when the record is rendered in HTML pages.

              -

              ANY_OF

              +

              ANY_OF

              This validator takes a list of validators and accepts a value if any of the validators in the list does (i.e. it acts like a logical OR with respect to given validators).

              requires = ANY_OF([IS_ALPHANUMERIC(), IS_EMAIL()])
              @@ -1195,7 +1196,7 @@ 

              ANY_OF

              -

              IS_IMAGE

              +

              IS_IMAGE

              This validator checks if a file uploaded through the file input was saved in one of the selected image formats and has dimensions (width and height) within given limits.

              It does not check for maximum file size (use IS_LENGTH for that). It returns @@ -1230,7 +1231,7 @@

              IS_IMAGE

              -

              IS_FILE

              +

              IS_FILE

              Checks if name and extension of file uploaded through file input matches given criteria.

              Does not ensure the file type in any way. Returns validation failure if no data was uploaded.

              Its arguments are:

              @@ -1270,7 +1271,7 @@

              IS_FILE<

              -

              IS_UPLOAD_FILENAME

              +

              IS_UPLOAD_FILENAME

              This is the older implementation for checking files, included for backwards compatibility. For new applications, use IS_FILE().

              This validator checks if the name and extension of a file uploaded through the file input matches the given criteria.

              It does not ensure the file type in any way. Returns validation failure @@ -1299,7 +1300,7 @@

              IS_UPLOAD_FILEN

              -

              IS_IPV4

              +

              IS_IPV4

              This validator checks if a field’s value is an IP version 4 address in decimal form. Can be set to force addresses from a certain range.

              IPv4 regex taken from regexlib. The signature for the IS_IPV4 constructor is the following:

              @@ -1347,7 +1348,7 @@

              IS_IPV4<

              -

              IS_IPV6

              +

              IS_IPV6

              This validator checks if a field’s value is an IP version 6 address.

              The signature for the IS_IPV6 constructor is the following:

              IS_IPV6(is_private=None,
              @@ -1394,7 +1395,7 @@ 

              IS_IPV6<

              -

              IS_IPADDRESS

              +

              IS_IPADDRESS

              This validator checks if a field’s value is an IP address (either version 4 or version 6). Can be set to force addresses from within a specific range. Checks are done using the appropriate IS_IPV4 or IS_IPV6 validator.

              @@ -1426,9 +1427,9 @@

              IS_IPADDRESS

              -

              Other validators

              +

              Other validators

              -

              CLEANUP

              +

              CLEANUP

              This is a filter. It never fails. By default it just removes all characters whose decimal ASCII codes are not in the list [10, 13, 32-127]. It always perform an initial strip (i.e. heading and trailing blank characters removal) on the value.

              requires = CLEANUP()
              @@ -1442,9 +1443,9 @@ 

              CLEANUP<

              -

              Database validators

              +

              Database validators

              -

              IS_NOT_IN_DB

              +

              IS_NOT_IN_DB

              Synopsis: IS_NOT_IN_DB(db|set, 'table.field')

              Consider the following example:

              @@ -1480,7 +1481,7 @@

              IS_NOT_IN_DB

              -

              IS_IN_DB

              +

              IS_IN_DB

              Synopsis: IS_IN_DB(db|set, 'table.value_field', '%(representing_field)s', zero='choose one') where the third and fourth arguments are optional.

              @@ -1539,7 +1540,7 @@

              IS_IN_DB

              -

              IS_IN_DB and Tagging

              +

              IS_IN_DB and Tagging

              The IS_IN_DB validator has an optional attribute multiple=False. If set to True multiple values can be stored in one field. This field should be of type list:reference as discussed in list:<type> and contains. An explicit example of tagging is discussed there. Multiple references are handled automatically in create and update forms, but they are transparent to @@ -1547,7 +1548,7 @@

              IS_IN_DB

              -

              Validation functions

              +

              Validation functions

              In order to explicitly define a validation function, we pass to the validation parameter a function that takes the form and returns a dictionary, mapping field names to errors. If the dictionary is non-empty, the errors will be @@ -1557,20 +1558,15 @@

              Validation functionsfrom py4web.utils.form import Form, FormStyleBulma from pydal.validators import IS_INT_IN_RANGE -def check_nonnegative_quantity(form): - if not form.errors and form.vars['product_quantity'] % 2: - form.errors['product_quantity'] = T('The product quantity must be even') +def custom_check(form): + if not 'name' in form.errors and len(form.vars['name']) < 4 + form.errors['name'] = T("too short") @action('form_example', method=['GET', 'POST']) @action.uses('form_example.html', session) def form_example(): - form = Form([ - Field('product_name'), - Field('product_quantity', 'integer', requires=IS_INT_IN_RANGE(0,100))], - validation=check_nonnegative_quantity, - formstyle=FormStyleBulma) + form = Form(db.thing, validation=custom_check) if form.accepted: - # Do something with form.vars['product_name'] and form.vars['product_quantity'] redirect(URL('index')) return dict(form=form) @@ -1590,7 +1586,7 @@

              Validation functions
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Built with
              Sphinx using a @@ -1615,7 +1611,7 @@

              Validation functions - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/en/chapter-13.html b/apps/_documentation/static/en/chapter-13.html index 996dd4653..24e21f278 100644 --- a/apps/_documentation/static/en/chapter-13.html +++ b/apps/_documentation/static/en/chapter-13.html @@ -1,25 +1,27 @@ - + - + - Authentication and authorization — py4web 1.20230507.1 documentation - - - - + Authentication and authorization — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
              - 1.20230507.1 + 20240915
              @@ -56,7 +58,7 @@
            • Help, resources and hints
            • Installation and Startup
            • The Dashboard
            • -
            • Creating your first app
            • +
            • Creating an app
            • Fixtures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -71,6 +73,7 @@
            • Two Factor Authentication
            • @@ -87,6 +90,7 @@
            • Authorization using Tags
            • @@ -121,7 +125,7 @@
              -

              Authentication and authorization

              +

              Authentication and authorization

              Strong authentication and authorization methods are vital for a modern, multiuser web application. While they are often used interchangeably, authentication and authorization @@ -131,7 +135,7 @@

              Authentication and authorization -

              Authentication using Auth

              +

              Authentication using Auth

              py4web comes with a an object Auth and a system of plugins for user authentication. It has the same name as the corresponding web2py one and serves the same purpose but the API and @@ -181,7 +185,7 @@

              Authentication using Auth -

              Auth UI

              +

              Auth UI

              You can create your own web UI to login users using the above APIs but py4web provides one as an example, implemented in the following files:

              Here @action.uses(auth.user) tells py4web that this action requires a logged in user and should redirect to login if no user is logged in.

              -

              Two Factor Authentication

              -

              Two factor authentication (or Two-step verification) is a way of improving authentication security. When activated an extra step is added in the login process. In the first step, users are shown the standard username/password form. If they successfully pass this challenge by submitting the correct username and password, and two factor authentication is enabled for the user, the server will present a second form before logging them in.

              +

              Two Factor Authentication

              +

              Two factor authentication (or Two-step verification) is a way of improving authentication security. When activated an extra step is added in the login +process. In the first step, users are shown the standard username/password form. If they successfully pass this challenge by submitting the correct +username and password, and two factor authentication is enabled for the user, the server will present a second form before logging them in.

              There are a few Auth settings available to control how two factor authentication works.

              -

              The follow can be specified on Auth instantiation:

              +

              The following can be specified on Auth instantiation:

                -
              • two_factor_required

              • -
              • two_factor_send

              • +
              • two_factor_required

              • +
              • two_factor_send

              • +
              • two_factor_validate

              -

              two_factor_required

              -

              When you pass a method name to the two_factor_filter parameter you are telling py4web to call that method to determine whether or not this login should be use or bypass two factor authentication. If your method returns True, then this login requires two factor. If it returns False, two factor authentication is bypassed for this login.

              -

              Sample two_factor_filter method

              +

              two_factor_required

              +

              When you pass a method name to the two_factor_required parameter you are telling py4web to call that method to determine whether or not this login should +be use or bypass two factor authentication. If your method returns True, then this login requires two factor. If it returns False, two factor authentication +is bypassed for this login.

              +

              Sample two_factor_required method

              This example shows how to allow users that are on a specific network.

              def user_outside_network(user, request):
                   import ipaddress
              @@ -280,8 +289,9 @@ 

              two_factor_required -

              two_factor_send

              -

              When two factor authentication is active, py4web generates a 6 digit code (using random.randint) and sends it to you. How this code is sent, is up to you. The two_factor_send argument to the Auth class allows you to specify the method that sends the two factor code to the user.

              +

              two_factor_send

              +

              When two factor authentication is active, py4web can generate a 6 digit code (using random.randint) and makes it possible to send it to the user. How this code is +sent, is up to you. The two_factor_send argument to the Auth class allows you to specify the method that sends the two factor code to the user.

              This example shows how to send an email with the two factor code:

              def send_two_factor_email(user, code):
                   try:
              @@ -296,15 +306,56 @@ 

              two_factor_sendreturn code

              -

              Notice that this method takes to arguments: the current user, and the code to be sent. +

              Notice that this method takes two arguments: the current user, and the code to be sent. Also notice this method can override the code and return a new one.

              auth.param.two_factor_required = user_outside_network
               auth.param.two_factor_send = send_two_factor_email
               
              +
              +

              two_factor_validate

              +

              By default, py4web will validate the user input in the two factor form by comparing the code entered by the user with the code generated and sent using +two_factor_send. However, sometimes it may be useful to define a custom validation of this user-entered code. For instance, if one would like to use the +TOTP (or the Time-Based One-Time-Passwords) as the two factor authentication method, the validation requires comparing the code entered by the user with the +value generated at the same time at the server side. Hence, it is not sufficient to generate that value earlier when showing the form (using for instance +two_factor_send method), because by the time the user submits the form, the current valid value may already be different. Instead, this value should be +generated when validating the form submitted by the user.

              +

              To accomplish such custom validation, the two_factor_validate method is available. It takes two arguments - the current user and the code that was entered +by the user into the two factor authentication form. The primary use-case for this method is validation of time-based passwords.

              +

              This example shows how to validate a time-based two factor code:

              +
              def validate_code(user, code):
              +   try:
              +      # get the correct code from an external function
              +      correct_code = generate_time_based_code(user_id)
              +   except Exception as e:
              +      # return None to indicate that validation could not be performed
              +      return None
              +
              +   # compare the value entered in the auth form with the correct code
              +   if code == correct_code:
              +      return True
              +   else:
              +      return False
              +
              +
              +

              The validate_code method must return one of three values:

              +
                +
              • True - if the validation succeded,

              • +
              • False - if the validation failed,

              • +
              • None - if the validation was not possible for any reason

              • +
              +

              Notice that - if defined - this method is _always_ called to validate the two factor authentication form. It is up to you to decide what kind of validation it +does. If the returned value is True, the user input will be accepted as valid. If the returned value is False then the user input will be rejected as +invalid, number of tries will be decresed by one, and user will be asked to try again. If the returned value is None the user input will be checked against +the code generated with the use of two_factor_send method and the final result will depend on that comparison. In this case authentication will fail if two_factor_send +method was not defined, and hence no code was sent to the user.

              +
              auth.param.two_factor_validate = validate_code
              +
              +
              +
              -

              two_factor_tries

              +

              two_factor_tries

              By default, the user has 3 attempts to pass two factor authentication. You can override this after using:

              -

              Auth Plugins

              +

              Auth Plugins

              Plugins are defined in “py4web/utils/auth_plugins” and they have a hierarchical structure. Some are exclusive and some are not. For example, default, LDAP, PAM, and SAML are exclusive (the developer has to pick @@ -339,7 +398,7 @@

              Auth Plugins -

              PAM

              +

              PAM

              Configuring PAM is the easiest:

              from py4web.utils.auth_plugins.pam_plugin import PamPlugin
               auth.register_plugin(PamPlugin())
              @@ -358,7 +417,7 @@ 

              PAM

              -

              LDAP

              +

              LDAP

              This is a common authentication method, especially using Microsoft Active Directory in enterprises.

              from py4web.utils.auth_plugins.ldap_plugin import LDAPPlugin
               LDAP_SETTING = {
              @@ -376,7 +435,7 @@ 

              LDAP

              -

              OAuth2 with Google

              +

              OAuth2 with Google

              from py4web.utils.auth_plugins.oauth2google import OAuth2Google # TESTED
               auth.register_plugin(OAuth2Google(
                   client_id=CLIENT_ID,
              @@ -387,7 +446,7 @@ 

              OAuth2 with Google -

              OAuth2 with Facebook

              +

              OAuth2 with Facebook

              from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED
               auth.register_plugin(OAuth2Facebook(
                   client_id=CLIENT_ID,
              @@ -398,7 +457,7 @@ 

              OAuth2 with FacebookThe client id and client secret must be provided by Facebook.

              -

              OAuth2 with Discord

              +

              OAuth2 with Discord

              from py4web.utils.auth_plugins.oauth2discord import OAuth2Discord
               auth.register_plugin(OAuth2Discord(
                   client_id=DISCORD_CLIENT_ID,
              @@ -418,12 +477,13 @@ 

              OAuth2 with Discord -

              Authorization using Tags

              +

              Authorization using Tags

              As already mentioned, authorization is the process of verifying what specific applications, files, and data a user has access to. This is accomplished -in py4web using Tags.

              +in py4web using Tags, that we’ve already discovered on Tagging records +in the DAL chapter.

              -

              Tags and Permissions

              +

              Tags and Permissions

              Py4web provides a general purpose tagging mechanism that allows the developer to tag any record of any table, check for the existence of tags, as well as checking for records @@ -440,12 +500,13 @@

              Tags and PermissionsTo use the tagging system you first need to import the Tags module from pydal.tools. Then create a Tags object to tag a table:

              from pydal.tools.tags import Tags
              -groups = Tags(db.auth_user)
              +groups = Tags(db.auth_user, 'groups')
               
              -

              If you look at the database level, a new table will be created with a -name equals to tagged_db + ‘_tag’ + tagged_name, in this case -auth_user_tag_groups:

              +

              The tail_name parameter is optional and if not specified the ‘default’ +value will be used. If you look at the database level, a new table will +be created with a name equals to tagged_db + '_tag_' + tail_name, +in this case auth_user_tag_groups:

              _images/tags_db.png

              Then you can add one or more tags to records of the table as well as remove existing tags:

              @@ -482,6 +543,24 @@

              Tags and Permissionsreturn {'users': users}

              +

              We’ve already seen a simple requires_membership fixture on :ref:The Condition fixture. It +enables the following syntax:

              +
              groups = Tags(db.auth_user)
              +
              +class requires_membership(Fixture):
              +    def __init__(self, group):
              +        self.__prerequisites__ = [auth.user] # you must have a user before you can check
              +        self.group  = group # store the group when action defined
              +    def on_request(self, context): # will be called if the action is called
              +        if self.group not in groups.get(auth.user_id):
              +            raise HTTP(401) # check and do something
              +
              +@action('index')
              +@action.uses(requires_membership('teacher'))
              +def index():
              +    return 'hello teacher'
              +
              +

              We leave it to you as an exercise to create a fixture has_membership to enable the following syntax:

              -

              Multiple Tags objects

              +

              Multiple Tags objects

              Note

              One table can have multiple associated Tags objects. The @@ -545,6 +624,29 @@

              Multiple Tags objects

              +
              +

              User Impersonation

              +

              Auth provides API that allow you to impersonate another user. +Here is an example of an action to start impersonating and stop impersonating another user.

              +
              @action("impersonate/{user_id:int}", method="GET")
              +@action.uses(auth.user)
              +def start_impersonating(user_id):
              +    if (not auth.is_impersonating() and
              +        user_id and
              +        user_id != auth.user_id and
              +        db(db.auth_user.id==user_id).count()):
              +        auth.start_impersonating(user_id, URL("index"))
              +    raise HTTP(404)
              +
              + @action("stop_impersonating", method="GET")
              + @action.uses(auth)
              + def stop_impersonating():
              +    if auth and auth.is_impersonating():
              +        auth.stop_impersonating(URL("index"))
              +    redirect(URL("index"))
              +
              +
              +
              @@ -559,7 +661,7 @@

              Multiple Tags objects
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Built with
              Sphinx using a @@ -584,7 +686,7 @@

              Multiple Tags objects - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/en/chapter-14.html b/apps/_documentation/static/en/chapter-14.html index 76b77ab05..91a7ea451 100644 --- a/apps/_documentation/static/en/chapter-14.html +++ b/apps/_documentation/static/en/chapter-14.html @@ -1,25 +1,27 @@ - + - + - Grid — py4web 1.20230507.1 documentation - - - - + Grid — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
              - 1.20230507.1 + 20240915
              @@ -56,7 +58,7 @@
            • Help, resources and hints
            • Installation and Startup
            • The Dashboard
            • -
            • Creating your first app
            • +
            • Creating an app
            • Fixtures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -82,6 +84,7 @@
            • Reference Fields
            • +
            • Grids with checkboxes
            • From web2py to py4web
            • @@ -113,12 +116,12 @@
              -

              Grid

              +

              Grid

              py4web comes with a Grid object providing grid and CRUD (create, update and delete) capabilities. This allows you to quickly and safely provide an interface to your data. Since it’s also highly customizable, it’s the corner stone of most py4web’s applications.

              -

              Key features

              +

              Key features

              • Full CRUD with Delete Confirmation

              • Click column heads for sorting - click again for DESC

              • @@ -137,14 +140,14 @@

                Key features -

                Basic grid example

                +

                Basic grid example

                In this simple example we will make a grid over the superhero table.

                Create a new minimal app called grid. Change it with the following content.

                # in grid/__init__.py
                 import os
                 from py4web import action, Field, DAL
                -from py4web.utils.grid import Grid, GridClassStyleBulma
                -from py4web.utils.form import Form, FormStyleBulma
                +from py4web.utils.grid import *
                +from py4web.utils.form import *
                 from yatl.helpers import A
                 
                 
                @@ -249,7 +252,7 @@ 

                Basic grid examplehttps://github.com/jpsteil/grid_tutorial.

              -

              The Grid object

              +

              The Grid object

              class Grid:
                  def __init__(
                     self,
              @@ -338,7 +341,7 @@ 

              The Grid objectUsing callable parameters later on.

              -

              Searching and filtering

              +

              Searching and filtering

              There are two ways to build a search form:

              • Provide a search_queries list

              • @@ -356,7 +359,7 @@

                Searching and filtering

              -

              CRUD settings

              +

              CRUD settings

              The grid provides CRUD (create, read, update and delete) capabilities utilizing py4web Form. You can turn off CRUD features by setting @@ -367,7 +370,7 @@

              CRUD settings -

              Custom columns

              +

              Custom columns

              If the grid does not involve a join but displays results from a single table you can specify a list of columns. Columns are highly customizable.

              from py4web.utils.grid import Column
              @@ -402,7 +405,7 @@ 

              Custom columns -

              Using templates

              +

              Using templates

              Use the following to render your grid or CRUD forms in your templates.

              Display the grid or a CRUD Form

              [[=grid.render()]]
              @@ -433,7 +436,7 @@ 

              Using templates -

              Customizing style

              +

              Customizing style

              You can provide your own formstyle or grid classes and style to grid.

              • formstyle is the same as a Form formstyle, used to style the CRUD @@ -448,7 +451,7 @@

                Customizing style -

                Custom Action Buttons

                +

                Custom Action Buttons

                As with web2py, you can add additional buttons to each row in your grid. You do this by providing pre_action_buttons or post_action_buttons to the Grid init method.

                @@ -461,7 +464,7 @@

                Custom Action ButtonsYou can build your own Action Button class to pass to pre/post action buttons based on the template below (this is not provided with py4web).

                -

                Sample Action Button Class

                +

                Sample Action Button Class

                class GridActionButton:
                  def __init__(
                      self,
                @@ -526,7 +529,7 @@ 

                Sample Action Button Class -

                Using callable parameters

                +

                Using callable parameters

                A recent improvement to py4web allows you to pass a callable instead of a GridActionButton. This allow you to more easily change the behaviour of standard and custom Actions.

                Callable can be used with:

                @@ -576,7 +579,7 @@

                Using callable parameters -

                Reference Fields

                +

                Reference Fields

                When displaying fields in a PyDAL table, you sometimes want to display a more descriptive field than a foreign key value. There are a couple of ways to handle that with the py4web grid.

                @@ -612,6 +615,39 @@

                Reference Fields +

                Grids with checkboxes

                +

                While the grid, per se, does not support checkboxes, you can use custom columns to add one or more columns of checkboxes. +You can also add the helpers logic (the grid uses helpers to generate HTML) to wrap it in a <form> and add one +or more submit buttons. You can then add logic to process the selected rows when the button is selected. For example:

                +
                column = Column("select", lambda row: INPUT(_type="checkbox",_name="selected_id",_value=row.id))
                +
                +@action("manage")
                +@action("manage/<path:path>")
                +@action.uses("manage.html", db)
                +def manage(path=None):
                +
                +   grid = Grid(path, db.thing, columns=[column, db.thing.name])
                +
                +   # if we are displaying a "select" grid page (not a form)
                +   if not grid.form:
                +      grid = grid.render()
                +      # if checkboxes selection was submitted
                +      if request.method == "POST":
                +         # do something with the selected ids
                +         print("you selected", request.POST.get("selected_id"))
                +      # inject a ``<form>`` and a ``submit`` button
                +      grid.children[1:] = [FORM(
                +            *grid.children[1:],
                +            DIV(INPUT(_type="submit",_value="do it!")),
                +            _method="POST",
                +            _action=request.url)]
                +   return locals()
                +
                +
                +

                Notice in the above example request.POST.get("selected_id") can be a single ID (if one selected) or a list of IDs (if more than one +is selected).

                +

              @@ -625,7 +661,7 @@

              Reference Fields -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Built with Sphinx using a @@ -650,7 +686,7 @@

              Reference Fields - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/en/chapter-15.html b/apps/_documentation/static/en/chapter-15.html index 37ce3a78e..ac7da9289 100644 --- a/apps/_documentation/static/en/chapter-15.html +++ b/apps/_documentation/static/en/chapter-15.html @@ -1,25 +1,27 @@ - + - + - From web2py to py4web — py4web 1.20230507.1 documentation - - - - + From web2py to py4web — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -40,7 +42,7 @@
              - 1.20230507.1 + 20240915
              @@ -56,7 +58,7 @@
            • Help, resources and hints
            • Installation and Startup
            • The Dashboard
            • -
            • Creating your first app
            • +
            • Creating an app
            • Fixtures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -111,7 +113,7 @@
              -

              From web2py to py4web

              +

              From web2py to py4web

              This chapter is dedicated to help users for porting old web2py applications to py4web.

              Web2py and py4web share many similarities and some differences. For example they share the same database abstraction layer (pyDAL) which means pydal table definitions and queries are identical @@ -198,9 +200,9 @@

              From web2py to py4web
              -

              Simple conversion examples

              +

              Simple conversion examples

              -

              “Hello world” example

              +

              “Hello world” example

              web2py

              # in controllers/default.py
               def index():
              @@ -216,7 +218,7 @@ 

              “Hello world” example

              -

              “Redirect with variables” example

              +

              “Redirect with variables” example

              web2py

              request.get_vars.name
               request.post_vars.name
              @@ -237,7 +239,7 @@ 

              “Redirect with variables” example -

              “Returning variables” example

              +

              “Returning variables” example

              web2py

              def index():
                  a = request.get_vars.a
              @@ -253,7 +255,7 @@ 

              “Returning variables” example -

              “Returning args” example

              +

              “Returning args” example

              web2py

              def index():
                  a, b, c = request.args
              @@ -269,7 +271,7 @@ 

              “Returning args” example -

              “Return calling methods” example

              +

              “Return calling methods” example

              web2py

              def index():
                  if request.method == "GET":
              @@ -291,7 +293,7 @@ 

              “Return calling methods” example -

              “Setting up a counter” example

              +

              “Setting up a counter” example

              web2py

              def counter():
                  session.counter = (session.counter or 0) + 1
              @@ -306,7 +308,7 @@ 

              “Setting up a counter” example -

              “View” example

              +

              “View” example

              web2py

              {{ extend 'layout.html' }}
               <div>
              @@ -327,7 +329,7 @@ 

              “View” example -

              “Form and flash” example

              +

              “Form and flash” example

              web2py

              db.define_table('thing', Field('name'))
               
              @@ -367,7 +369,7 @@ 

              “Form and flash” example -

              “grid” example

              +

              “grid” example

              web2py

              def index():
                  grid = SQLFORM.grid(db.thing, editable=True)
              @@ -385,7 +387,7 @@ 

              “grid” example -

              “Accessing OS files” example

              +

              “Accessing OS files” example

              web2py

              file_path = os.path.join(request.folder, 'file.csv')
               
              @@ -397,7 +399,7 @@

              “Accessing OS files” example -

              “auth” example

              +

              “auth” example

              web2py

              auth = Auth()
               auth.define_tables()
              @@ -463,7 +465,7 @@ 

              “auth” example -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Built with Sphinx using a @@ -488,7 +490,7 @@

              “auth” example - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/en/chapter-16.html b/apps/_documentation/static/en/chapter-16.html index 1d466aa17..53ba723fe 100644 --- a/apps/_documentation/static/en/chapter-16.html +++ b/apps/_documentation/static/en/chapter-16.html @@ -1,25 +1,27 @@ - + - + - Advanced topics and examples — py4web 1.20230507.1 documentation - - - - + Advanced topics and examples — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -39,7 +41,7 @@
              - 1.20230507.1 + 20240915
              @@ -55,7 +57,7 @@
            • Help, resources and hints
            • Installation and Startup
            • The Dashboard
            • -
            • Creating your first app
            • +
            • Creating an app
            • Fixtures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -67,6 +69,9 @@
            • Grid
            • From web2py to py4web
            • Advanced topics and examples
                +
              • The scheduler
              • +
              • Sending messages using a background task
              • +
              • Celery
              • py4web and asyncio
              • htmx
                • htmx usage in Form
                • @@ -109,9 +114,134 @@
                  -

                  Advanced topics and examples

                  +

                  Advanced topics and examples

                  +
                  +

                  The scheduler

                  +

                  Py4web has a built-in scheduler. There is nothing for you to install or configure to make it work.

                  +

                  Given a task (just a python function), you can schedule async runs of that function. +The runs can be a one-off or periodic. They can have timeout. They can be scheduled to run at a given scheduled time.

                  +

                  The scheduler works by creating a table task_run and enqueueing runs of the predefined task as table records. +Each task_run references a task and contains the input to be passed to that task. The scheduler will capture the +task stdout+stderr in a db.task_run.log and the task output in db.task_run.output.

                  +

                  A py4web thread loops and finds the next task that needs to be executed. For each task it creates a worker process +and assigns the task to the worker process. You can specify how many worker processes should run concurrently. +The worker processes are daemons and they only live for the life of one task run. Each worker process is only +responsible for executing that one task in isolation. The main loop is responsible for assigning tasks and timeouts.

                  +

                  The system is very robust because the only source of truth is the database and its integrity is guaranteed by +transactional safety. Even if py4web is killed, running tasks continue to run unless they complete, fail, or are +explicitly killed.

                  +

                  Aside for allowing multiple concurrent task runs in execution on one node, +it is also possible to run multiple instances of the scheduler on different computing nodes, +as long as they use the same client/server database for task_run and as long as +they all define the same tasks.

                  +

                  Here is an example of how to use the scheduler:

                  +
                  from pydal.tools.scheduler import Scheduler, delta, now
                  +from .common import db
                  +
                  +# create and start the scheduler
                  +scheduler = Scheduler(db, sleep_time=1, max_concurrent_runs=1)
                  +scheduler.start()
                  +
                  +# register your tasks
                  +scheduler.register_task("hello", lambda **inputs: print("hi!"))
                  +scheduler.register_task("slow", lambda: time.sleep(10))
                  +scheduler.register_task("periodic", lambda **inputs: print("I am periodic!"))
                  +scheduler.register_task("fail", lambda x: 1 / x)
                  +
                  +# enqueue some task runs:
                  +
                  +scheduler.enqueue_run(name="hello")
                  +scheduler.enqueue_run(name="hello", scheduled_for=now() + delta(10) # start in 10 secs
                  +scheduler.enqueue_run(name="slow", timeout=1) # 1 secs
                  +scheduler.enqueue_run(name="periodic", period=10) # 10 secs
                  +scheduler.enqueue_run(name="fail", inputs={"x": 0})
                  +
                  +
                  +

                  Notice that in scaffolding app, the scheduler is created and started in common if +USE_SCHEDULER=True in settings.py.

                  +

                  You can manage your task runs busing the dashboard or using a Grid(path, db.task_run).

                  +

                  To prevent database locks (in particular with sqlite) we recommend:

                  +
                    +
                  • Use a different database for the scheduler and everything else

                  • +
                  • Always db.commit() as soon as possible after any insert/update/delete

                  • +
                  • wrap your database logic in tasks in a try…except as in

                  • +
                  +
                  def my_task():
                  +    try:
                  +        # do something
                  +        db.commit()
                  +    except Exception:
                  +        db.rollback()
                  +
                  +
                  +
                  +
                  +

                  Sending messages using a background task

                  +

                  As en example of application of the above, consider the case of wanting to send emails asynchronously from a background task. +In this example we send them using SendGrid from Twilio (https://www.twilio.com/docs/sendgrid/for-developers/sending-email/quickstart-python).

                  +

                  Here is a possible scheduler task to send the email:

                  +
                  import sendgrid
                  +from sendgrid.helpers.mail import Mail, Email, To, Content
                  +
                  +def sendmail_task(from_addr, to_addrs, subject, body):
                  +    ""
                  +    # build the messages using sendgrid API
                  +    from_email = Email(from_addr)  # Must be your verified sender
                  +    content_type = "text/plain" if body[:6] != "<html>" else "text/html"
                  +    content = Content(content_type, body)
                  +    mail = Mail(from_email, To(to_addrs), subject, content)
                  +    # ask sendgrid to deliver it
                  +    sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
                  +    response = sg.client.mail.send.post(request_body=mail.get())
                  +    # check if worked
                  +    assert response.status_code == "200"
                  +
                  +# register the above task with the scheduler
                  +scheduler.register_task("sendmail", sendmail_task)
                  +
                  +
                  +

                  To schedule sending a new email do:

                  +
                  email = {
                  +    "from_addr": "me@example.com",
                  +    "to_addrs": ["me@example.com"],
                  +    "subject": "Hello World",
                  +    "body": "I am alive!",
                  +}
                  +scheduler.enqueue_run(name="sendmail", inputs=email, scheduled_for=None)
                  +
                  +
                  +

                  The key:value in the email representation must match the arguments of the task. +The scheduled_for argument is optional and allows you to specify when the email should be sent. +You can use the Dashboard to see the status of your task_runs for the task called sendmail.

                  +

                  You can also tell auth to tap into above mechanism for sending emails:

                  +
                  class MySendGridSender:
                  +    def __init__(self, from_addr):
                  +        self.from_addr = from_adds
                  +    def send(self, to_addr, subject, body):
                  +        email = {
                  +            "from_addr": self.from_addr,
                  +            "to_addrs": [to_addr],
                  +            "subject": subject,
                  +            "body": body,
                  +        }
                  +        scheduler.enqueue_run(name="sendmail", inputs=email)
                  +
                  +auth.sender = MySendGridSender(from_addr="me@example.com")
                  +
                  +
                  +

                  With the above, Auth will not send emails using smtplib. Instead it will send them with SendGrid using the scheduler. +Notice the only requirement here is that auth.sender must be an object with a send method with the same signature as in the example.

                  +

                  Notice, it it also possible to send SMS messages instead of emails but this requires 1) store the phone number in auth_user and 2) override the Auth.send method.

                  +
                  +
                  +

                  Celery

                  +

                  Yes. You can use Celery instead of the build-in scheduler but it adds complexity and it is less robust. +Yet the build-in scheduler is designed for long running tasks and the database can become a bottleneck +if you have hundreds of tasks running concurrently. Celery may work better if you have more than 100 concurrent +tasks and/or they are short running tasks.

                  +
                  -

                  py4web and asyncio

                  +

                  py4web and asyncio

                  Asyncio is not strictly needed, at least for most of the normal use cases where it will add problems more than value because of its concurrency model. On the other hand, we think py4web needs a built-in websocket async based solution.

                  @@ -120,7 +250,7 @@

                  py4web and asyncio -

                  htmx

                  +

                  htmx

                  There are many javascript front-end frameworks available today that allow you great flexibility over how you design your web client. Vue, React and Angular are just a few. However, the complexity in building one of these systems prevents many developers from reaping those benefits. @@ -139,7 +269,7 @@

                  htmx
                • Includes an htmx attributes plugin for the py4web grid

                • -

                  htmx usage in Form

                  +

                  htmx usage in Form

                  The py4web Form class allows you to pass **kwargs to it that will be passed along as attributes to the html form. For example, to add the hx-post and hx-target to the <form> element you would use:

                  attrs = {
                  @@ -231,7 +361,7 @@ 

                  htmx usage in Form -

                  htmx usage in Grid

                  +

                  htmx usage in Grid

                  The py4web grid provides an attributes plugin system that allows you to build plugins to provide custom attributes for form elements, anchor elements or confirmation messages. py4web also provide an attributes plugin specifically for htmx.

                  @@ -302,7 +432,7 @@

                  htmx usage in Grid -

                  Autocomplete Widget using htmx

                  +

                  Autocomplete Widget using htmx

                  htmx can be used for much more than just form/grid processing. In this example we’ll take advantage of htmx and the py4web form widgets to build an autocomplete widget that can be used in your forms. NOTE: this is just an example, none of this code comes with py4web

                  @@ -506,18 +636,18 @@

                  Autocomplete Widget using htmx -

                  utils.js

                  +

                  utils.js

                  Multiple times in this documentation we have mentioned utils.js which comes with the scaffolding application, yet we never clearly listed what is in there. So here it is.

                  -

                  string.format

                  +

                  string.format

                  It extends the String object prototype to allow expressions like this:

                  var a = "hello {name}".format(name="Max");
                   
                  -

                  The Q object

                  +

                  The Q object

                  The Q object can be used like a selector supporting jQuery like syntax:

                  var element = Q("#element-id")[0];
                   var selected_elements = Q(".element-class");
                  @@ -601,7 +731,7 @@ 

                  The Q object -

                  The T object

                  +

                  The T object

                  This is a Javascript reimplementation of the Python pluralize library in Python which is used by the Python T object in py4web. So basically a client-side T.

                  T.translations = {'dog': {0: 'no cane', 1: 'un case', 2: '{n} cani', 10: 'tanti cani'}};
                  @@ -731,7 +861,7 @@ 

                  The T object -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Built with Sphinx using a @@ -756,7 +886,7 @@

                  The T object - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/en/genindex.html b/apps/_documentation/static/en/genindex.html index 4e0a3b464..68a1c04c0 100644 --- a/apps/_documentation/static/en/genindex.html +++ b/apps/_documentation/static/en/genindex.html @@ -1,25 +1,27 @@ - + - Index — py4web 1.20230507.1 documentation - - - - + Index — py4web 20240915 documentation + + + + + + - - - - - - - + + + + + + + @@ -38,7 +40,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -54,7 +56,7 @@
                • Help, resources and hints
                • Installation and Startup
                • The Dashboard
                • -
                • Creating your first app
                • +
                • Creating an app
                • Fixtures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -107,7 +109,7 @@

                  Index


                  -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Built with Sphinx using a @@ -132,7 +134,7 @@

                  Index

                  - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/en/index.html b/apps/_documentation/static/en/index.html index fdf8655db..ef9ebe901 100644 --- a/apps/_documentation/static/en/index.html +++ b/apps/_documentation/static/en/index.html @@ -1,25 +1,27 @@ - + - + - py4web: the reference Manual — py4web 1.20230507.1 documentation - - - - + py4web: the reference Manual — py4web 20240915 documentation + + + + + + - - - - - - + + + + + + @@ -39,7 +41,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -55,7 +57,7 @@
                • Help, resources and hints
                • Installation and Startup
                • The Dashboard
                • -
                • Creating your first app
                • +
                • Creating an app
                • Fixtures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -94,7 +96,7 @@
                  -

                  py4web: the reference Manual

                  +

                  py4web: the reference Manual

                  Built with Sphinx using a @@ -275,7 +283,7 @@

                  Indices and tables - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/en/objects.inv b/apps/_documentation/static/en/objects.inv index 808b02451..86f2301c8 100644 Binary files a/apps/_documentation/static/en/objects.inv and b/apps/_documentation/static/en/objects.inv differ diff --git a/apps/_documentation/static/en/search.html b/apps/_documentation/static/en/search.html index 1721874fd..9395416d0 100644 --- a/apps/_documentation/static/en/search.html +++ b/apps/_documentation/static/en/search.html @@ -1,26 +1,28 @@ - + - Search — py4web 1.20230507.1 documentation - - - - + Search — py4web 20240915 documentation + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -57,7 +59,7 @@
                • Help, resources and hints
                • Installation and Startup
                • The Dashboard
                • -
                • Creating your first app
                • +
                • Creating an app
                • Fixtures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -114,7 +116,7 @@
                  -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Built with Sphinx using a @@ -139,7 +141,7 @@ - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/en/searchindex.js b/apps/_documentation/static/en/searchindex.js index f9e569e77..d2dd50c44 100644 --- a/apps/_documentation/static/en/searchindex.js +++ b/apps/_documentation/static/en/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["chapter-01", "chapter-02", "chapter-03", "chapter-04", "chapter-05", "chapter-06", "chapter-07", "chapter-08", "chapter-09", "chapter-10", "chapter-11", "chapter-12", "chapter-13", "chapter-14", "chapter-15", "chapter-16", "index"], "filenames": ["chapter-01.rst", "chapter-02.rst", "chapter-03.rst", "chapter-04.rst", "chapter-05.rst", "chapter-06.rst", "chapter-07.rst", "chapter-08.rst", "chapter-09.rst", "chapter-10.rst", "chapter-11.rst", "chapter-12.rst", "chapter-13.rst", "chapter-14.rst", "chapter-15.rst", "chapter-16.rst", "index.rst"], "titles": ["What is py4web?", "Help, resources and hints", "Installation and Startup", "The Dashboard", "Creating your first app", "Fixtures", "The Database Abstraction Layer (DAL)", "The RestAPI", "YATL Template Language", "YATL helpers", "Internationalization", "Forms", "Authentication and authorization", "Grid", "From web2py to py4web", "Advanced topics and examples", "py4web: the reference Manual"], "terms": {"web": [0, 1, 2, 5, 6, 8, 12, 13, 14, 15, 16], "framework": [0, 1, 2, 4, 5, 11, 12, 13, 14, 15], "rapid": [0, 11, 15], "develop": [0, 1, 4, 5, 6, 12, 13, 14, 15], "effici": [0, 1, 5, 6, 14], "databas": [0, 1, 3, 4, 7, 12, 13, 14, 15, 16], "driven": 0, "applic": [0, 2, 3, 4, 5, 8, 11, 12, 13, 14, 15], "It": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15], "an": [0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 14, 15], "evolut": 0, "popular": [0, 6], "web2pi": [0, 1, 2, 3, 4, 6, 11, 12, 13, 16], "much": [0, 1, 4, 5, 6, 8, 11, 13, 14, 15], "faster": [0, 6, 8, 14], "slicker": 0, "Its": [0, 2, 6, 11, 13], "intern": [0, 1, 4, 5, 6, 8, 12, 13], "design": [0, 4, 6, 8, 12, 15, 16], "ha": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "been": [0, 2, 5, 6, 8, 11], "simplifi": [0, 4, 6, 8], "compar": [0, 6, 14], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "seen": [0, 5, 6, 7, 9, 11, 13, 15], "competitor": 0, "other": [0, 1, 2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15, 16], "like": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "django": [0, 1, 14], "flask": [0, 14], "inde": [0, 5], "serv": [0, 2, 4, 6, 12, 14, 15], "same": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "purpos": [0, 4, 5, 6, 9, 11, 12, 14], "yet": [0, 2, 4, 5, 6, 8, 11, 15], "aim": 0, "provid": [0, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "larger": 0, "featur": [0, 2, 4, 7, 8, 14, 16], "set": [0, 1, 2, 4, 5, 7, 8, 9, 10, 12, 15], "out": [0, 1, 6, 8, 15], "box": [0, 6, 11], "reduc": [0, 5, 15], "time": [0, 2, 4, 5, 8, 13, 14, 15], "new": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "app": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "from": [0, 1, 3, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16], "histor": [0, 6], "perspect": 0, "our": [0, 1, 2, 4, 5, 6, 9, 11, 15], "stori": [0, 5], "start": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15], "2007": 0, "when": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "wa": [0, 5, 6, 11, 12], "first": [0, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16], "releas": [0, 2], "all": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "inclus": [0, 11], "solut": [0, 1, 2, 6, 15], "one": [0, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 15], "zip": [0, 2, 6, 11], "file": [0, 1, 2, 3, 5, 6, 8, 9, 12, 13, 15, 16], "contain": [0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "python": [0, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15], "interpret": [0, 6, 7, 9], "base": [0, 2, 3, 4, 5, 7, 8, 11, 12, 13, 14, 15], "id": [0, 1, 2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15], "collect": [0, 2, 14], "battl": 0, "test": [0, 2, 4, 6, 8, 9, 11, 12, 13], "packag": [0, 6, 9], "work": [0, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "well": [0, 5, 6, 8, 11, 12, 15], "togeth": [0, 6, 8, 10, 13], "In": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "mani": [0, 1, 2, 4, 5, 7, 8, 11, 13, 14, 15], "wai": [0, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "immens": 0, "success": [0, 4, 5, 6, 7, 12, 15], "succeed": 0, "low": 0, "barrier": 0, "entri": [0, 6, 7, 10, 11, 13, 14], "veri": [0, 4, 5, 6, 8, 9, 11, 12, 14], "secur": [0, 2, 7, 12], "platform": [0, 1, 16], "remain": [0, 6, 11], "backward": [0, 6, 11], "compat": [0, 1, 6, 9, 11], "until": [0, 5, 6, 8, 11], "todai": [0, 11, 15], "alwai": [0, 2, 4, 5, 6, 8, 11, 15], "suffer": [0, 12], "problem": [0, 1, 6, 8, 12, 13, 15], "its": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "monolith": 0, "The": [0, 2, 8, 9, 10, 12, 14, 16], "most": [0, 3, 4, 5, 6, 11, 12, 13, 14, 15], "experienc": 0, "did": [0, 4, 6, 15], "understand": [0, 1, 4, 6, 7, 8, 13, 16], "how": [0, 2, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16], "us": [0, 1, 3, 4, 7, 10, 11, 14, 16], "compon": [0, 3, 4, 5, 8, 9, 11, 12, 15], "outsid": [0, 5, 6, 11, 13, 14, 15], "third": [0, 5, 6, 11], "parti": [0, 5], "within": [0, 1, 4, 5, 6, 8, 9, 11, 13, 15], "we": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "thought": [0, 6, 12], "perfect": 0, "tool": [0, 6, 12, 15], "have": [0, 1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], "broken": [0, 11], "piec": [0, 5, 6, 8, 11], "becaus": [0, 2, 4, 5, 6, 8, 9, 10, 11, 13, 15], "would": [0, 5, 6, 8, 9, 11, 13, 15], "compromis": 0, "turn": [0, 4, 6, 12, 13, 15], "were": [0, 4, 6], "wrong": [0, 5, 6], "plai": [0, 15], "import": [0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "henc": [0, 2, 4, 5, 6, 11], "sinc": [0, 2, 4, 5, 6, 7, 8, 11, 12, 13, 14], "2015": 0, "three": [0, 5, 6], "front": [0, 8, 11, 15], "port": [0, 2, 3, 5, 6, 14], "3": [0, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "broke": 0, "modul": [0, 1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 16], "independ": [0, 5, 6, 12], "reassembl": 0, "some": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 14], "those": [0, 2, 5, 6, 8, 9, 12, 14, 15], "more": [0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], "modular": [0, 6, 8, 14], "than": [0, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15], "repackag": 0, "complet": [0, 6, 8, 9, 11, 12], "redesign": 0, "them": [0, 1, 2, 4, 5, 6, 7, 8, 11, 12, 13, 14], "case": [0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 14, 15], "better": [0, 1, 2, 4, 6, 13, 14], "function": [0, 2, 4, 5, 6, 7, 9, 13, 14, 15], "remov": [0, 2, 4, 6, 9, 11, 12], "ad": [0, 2, 5, 10, 11, 12, 13, 15], "tri": [0, 6, 14], "preserv": [0, 5], "syntax": [0, 1, 4, 5, 6, 7, 9, 11, 12, 14, 15, 16], "user": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "love": 0, "here": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15], "explicit": [0, 2, 4, 5, 6, 11, 12], "list": [0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15], "see": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 15], "detail": [0, 1, 2, 5, 6, 7, 8, 9, 11, 13, 15], "you": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "come": [0, 4, 5, 6, 8, 11, 12, 13, 14, 15], "unlik": [0, 2, 4, 6, 7, 8, 14, 15], "requir": [0, 1, 2, 4, 5, 6, 7, 11, 12, 13, 14, 15], "instal": [0, 1, 3, 4, 5, 6, 12, 16], "pip": [0, 1, 6], "depend": [0, 2, 3, 4, 5, 6, 8, 11, 13, 14], "ar": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "manag": [0, 1, 2, 3, 4, 5, 6, 11, 12], "txt": [0, 2, 4, 6], "regular": [0, 2, 4, 6, 7, 8, 10, 11, 13, 14, 15], "thi": [0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "differ": [0, 2, 5, 6, 8, 10, 11, 12, 13, 14, 15], "particular": [0, 1, 6, 11, 13, 14, 15], "ditch": 0, "custom": [0, 2, 4, 8, 14, 15, 16], "reli": [0, 4, 6], "now": [0, 2, 4, 5, 6, 8, 11, 13, 15], "exclus": [0, 5, 6, 11, 12], "mechan": [0, 5, 6, 8, 9, 12, 14], "multipl": [0, 1, 2, 4, 6, 8, 9, 10, 11, 14, 15, 16], "concurr": [0, 2, 6, 11, 15], "long": [0, 11], "submodul": 0, "ombott": [0, 4, 14], "spin": [0, 4], "off": [0, 2, 4, 6, 13, 15], "bottl": [0, 4, 5, 8, 14], "request": [0, 1, 2, 5, 6, 7, 11, 12, 13, 14, 15], "object": [0, 5, 6, 7, 8, 9, 10, 11, 14, 16], "rout": [0, 3, 5, 13, 14, 15], "doe": [0, 2, 5, 6, 8, 11, 12, 13, 14, 15], "creat": [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 13, 14, 15, 16], "environ": [0, 1, 4, 5, 6, 14], "everi": [0, 2, 4, 5, 6, 12, 13, 14, 15], "introduc": [0, 8], "concept": [0, 12], "fixtur": [0, 2, 4, 6, 9, 12, 14, 16], "explicitli": [0, 2, 5, 6, 8, 9, 11], "declar": [0, 4, 5, 6], "which": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "need": [0, 1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "re": [0, 1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "initi": [0, 2, 4, 5, 8, 11], "http": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "arriv": [0, 6], "cleanup": 0, "make": [0, 1, 2, 5, 8, 9, 10, 11, 12, 13, 14, 15], "session": [0, 2, 4, 6, 9, 11, 12, 13, 14, 15, 16], "": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15], "strong": [0, 9, 12], "encrypt": [0, 2, 5], "data": [0, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16], "longer": [0, 6], "store": [0, 2, 5, 6, 9, 11, 12, 14, 15], "system": [0, 2, 5, 6, 9, 12, 15], "perform": [0, 3, 5, 6, 11, 12, 14], "issu": [0, 3, 6], "cooki": [0, 4, 6, 15], "redi": 0, "memcach": [0, 6], "option": [0, 3, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "also": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "limit": [0, 5, 6, 7, 8, 11, 12, 14, 15], "json": [0, 1, 2, 4, 5, 6, 7, 10, 11, 14, 15], "serializ": [0, 5, 6, 9], "built": [0, 1, 4, 6, 8, 11, 13, 15, 16], "ticket": [0, 3, 6], "global": [0, 5, 6, 8, 12, 14, 15], "per": [0, 4, 5, 7, 13], "filesystem": [0, 4, 5, 6], "individu": [0, 2, 5, 6, 7, 8, 11], "thei": [0, 2, 4, 5, 6, 7, 8, 11, 12, 14, 15], "singl": [0, 2, 5, 6, 7, 8, 9, 11, 12, 13], "pydal": [0, 2, 3, 4, 5, 6, 7, 11, 12, 13, 14, 15], "leverag": 0, "restapi": [0, 3, 16], "yatl": [0, 4, 5, 6, 11, 13, 15, 16], "templat": [0, 9, 11, 12, 14, 15, 16], "languag": [0, 1, 4, 5, 10, 14, 15, 16], "default": [0, 2, 3, 4, 5, 7, 9, 11, 12, 13, 14, 15], "squar": [0, 8], "bracket": [0, 8, 11], "delimit": [0, 5, 6, 8, 14], "avoid": [0, 1, 2, 5, 6, 8, 11], "conflict": [0, 2, 4, 6], "model": [0, 3, 4, 7, 9, 13, 14, 15], "j": [0, 4, 5, 8, 10, 14, 16], "vue": [0, 4, 15], "angular": [0, 15], "includ": [0, 4, 5, 6, 7, 9, 11, 13, 14, 15], "subset": [0, 6, 11], "helper": [0, 4, 5, 6, 8, 11, 13, 14, 16], "plural": [0, 4, 5, 13, 14, 15, 16], "librari": [0, 2, 4, 6, 10, 11, 12, 14, 15], "internation": [0, 4, 5, 11, 14, 16], "practic": [0, 6, 8, 11, 14, 16], "expos": [0, 3, 4, 5, 6, 7, 12], "t": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14], "similar": [0, 4, 5, 6, 9, 11, 14], "cach": [0, 2, 4, 11, 16], "flexibl": [0, 2, 6, 12, 13, 15], "capabl": [0, 13, 15], "dashboard": [0, 2, 4, 6, 8, 11, 16], "replac": [0, 2, 4, 6, 8, 9, 11, 13, 15], "admin": [0, 6, 14], "upload": [0, 4, 14], "edit": [0, 2, 3, 4, 8, 9, 11, 12, 13, 14, 15], "interfac": [0, 3, 5, 6, 13, 14, 15], "appadmin": [0, 6], "form": [0, 5, 6, 7, 10, 12, 13, 16], "grid": [0, 16], "sqlform": [0, 11, 14], "auth": [0, 2, 4, 6, 8, 9, 15, 16], "easier": [0, 6, 7, 8], "extend": [0, 5, 6, 12, 14, 15], "basic": [0, 1, 5, 6, 15, 16], "regist": [0, 4, 5, 6, 8, 12, 14], "login": [0, 2, 4, 5, 6, 8, 11, 12, 14, 16], "logout": [0, 8, 12], "chang": [0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "password": [0, 2, 3, 5, 6, 8, 11, 12, 14, 15], "profil": [0, 8, 12, 14], "integr": [0, 1, 8, 15], "pam": [0, 5], "saml2": 0, "ldap": [0, 5], "oauth2": [0, 5], "googl": [0, 3, 5, 8, 11, 13], "facebook": [0, 5], "twitter": [0, 5, 12], "tag": [0, 5, 7, 8, 14, 15, 16], "group": [0, 2, 3, 5, 12, 14], "search": [0, 1, 2, 3, 6, 9, 11, 12, 16], "appli": [0, 5, 6, 11, 12, 13, 15], "permiss": [0, 5, 6, 14], "membership": [0, 5, 11, 12, 14], "interact": [0, 5], "gener": [0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], "These": [0, 5, 6, 9, 11, 13], "api": [0, 6, 7, 9, 11, 12, 14, 15], "allow": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15], "server": [0, 2, 3, 4, 7, 8, 12, 14, 15, 16], "polici": [0, 16], "about": [0, 7, 12, 13, 14, 15, 16], "oper": [0, 3, 5, 10, 11, 12, 16], "client": [0, 7, 11, 12, 15], "give": [0, 2, 4, 5, 6, 8, 11, 12, 13], "constraint": [0, 6], "two": [0, 1, 2, 4, 5, 6, 8, 11, 13, 14, 15], "main": [0, 1, 2, 4, 6, 8, 13, 14, 15, 16], "mtabl": 0, "customiz": [0, 11, 13], "goal": 0, "easi": [0, 5, 6, 8, 9, 11], "access": [0, 2, 4, 5, 6, 9, 11, 12, 15], "while": [0, 5, 6, 7, 11, 12, 14], "produc": [0, 2, 5, 6, 8, 9, 11, 12], "fast": [0, 4, 15], "thank": [0, 6, 9], "everyon": [0, 1], "who": [0, 12], "contribut": [0, 16], "project": [0, 2, 4, 5, 6], "especi": [0, 2, 5, 6, 11, 12], "massimo": [0, 6], "di": [0, 5], "pierro": 0, "luca": [0, 1], "de": [0, 1, 6, 10], "alfaro": [0, 1], "cassio": 0, "botaro": 0, "dan": 0, "carrol": 0, "jim": [0, 1, 13], "steil": [0, 1, 13], "john": [0, 6], "m": [0, 2, 6, 9, 11], "wolf": 0, "micah": 0, "beaslei": 0, "nico": 0, "zanferrari": 0, "pirsch": 0, "sugizo": 0, "valq7711": [0, 4], "kevin": 0, "keller": 0, "sam": 0, "logo": [0, 8, 9], "special": [0, 4, 5, 6, 8, 9, 10, 12, 14, 16], "offici": [0, 6, 11, 15], "friendli": [0, 5, 8], "call": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15], "axel": 0, "axolotl": 0, "magic": 0, "repres": [0, 8, 11], "sens": [0, 6, 11, 12], "kind": [0, 4], "believ": [0, 5], "cornerston": 0, "grow": [0, 6, 15], "commun": [0, 5, 6], "ve": [1, 2, 3, 5, 8, 9, 11, 13, 15], "made": [1, 2, 6, 8, 11, 13], "best": [1, 2, 5, 6, 10, 13], "simpl": [1, 2, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16], "clean": [1, 9], "But": [1, 2, 4, 5, 6, 8, 9, 11, 13, 15], "know": [1, 2, 5, 6, 8, 13], "program": [1, 2, 3, 6, 8, 9, 11, 15], "i": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "daunt": 1, "task": [1, 4, 5, 6, 12], "open": [1, 2, 3, 4, 5, 6, 9], "mind": [1, 4, 6, 7, 8, 11], "abl": [1, 2, 5, 6, 11], "jump": 1, "frequent": [1, 6, 11], "without": [1, 2, 8, 9, 13, 15, 16], "being": [1, 5, 6, 7, 8, 11, 15], "lost": [1, 5, 6, 14], "html": [1, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15], "javascript": [1, 4, 7, 9, 15], "css": [1, 4, 5, 8, 9, 11, 12, 13, 15], "even": [1, 4, 5, 6, 8, 9, 11, 13, 14], "don": [1, 2, 3, 4, 6, 8, 11, 12, 13], "scare": 1, "ll": [1, 2, 3, 5, 6, 8, 11, 13, 15], "assist": 1, "side": [1, 6, 15, 16], "journei": 1, "And": [1, 4, 5, 6, 11, 12, 13, 15], "valuabl": 1, "go": [1, 2, 4, 6, 8, 13, 15], "show": [1, 2, 4, 5, 6, 8, 11, 12, 13], "refer": [1, 5, 7, 8, 11], "avail": [1, 2, 6, 8, 9, 12, 14, 15], "onlin": [1, 6], "com": [1, 3, 4, 8, 9, 12, 13, 15], "_document": [1, 3], "static": [1, 8, 9, 14, 16], "index": [1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16], "where": [1, 2, 4, 6, 7, 8, 11, 12, 14, 15], "find": [1, 2, 4, 8, 10, 11, 12, 13], "pdf": [1, 11], "ebook": 1, "version": [1, 3, 5, 7, 8, 11, 15], "written": [1, 8, 11, 13], "restructuredtext": 1, "sphinx": 1, "There": [1, 2, 5, 6, 8, 12, 13, 15], "dedic": [1, 4, 5, 9, 14], "mail": [1, 3, 6, 11], "host": [1, 2, 5, 11, 12], "g": [1, 6, 9, 11], "discuss": [1, 3, 6, 8, 9, 11], "For": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 15], "ani": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "should": [1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "face": [1, 6], "right": [1, 2, 3, 5, 6, 8], "place": [1, 5, 6, 8, 11, 13, 15], "quick": [1, 11], "question": [1, 2, 6], "chat": 1, "free": [1, 5, 6, 12], "could": [1, 2, 4, 6, 8, 9, 11, 12, 14], "usual": [1, 2, 5, 6, 7, 8, 9, 11], "hang": 1, "channel": 1, "learn": [1, 6], "site": [1, 2, 4, 5, 6, 9, 13, 15], "lot": [1, 4, 6], "excel": [1, 13], "train": 1, "cours": [1, 8], "2020": 1, "uc": 1, "santa": 1, "cruz": 1, "blog": [1, 6, 9], "andrew": 1, "gavgavian": 1, "replic": [1, 5], "famou": 1, "corei": 1, "schafer": 1, "seri": 1, "south": 1, "breez": 1, "enterpris": [1, 12], "demo": [1, 2, 15], "around": [1, 6, 14], "structur": [1, 4, 5, 6, 10, 12, 13, 14, 16], "microsoft": [1, 12], "northwind": 1, "convert": [1, 4, 6, 9, 11], "sqlite": [1, 4, 5, 7, 11, 12, 13], "view": [1, 5, 6], "final": [1, 2, 4, 6, 10, 13], "result": [1, 6, 7, 8, 11, 13, 15], "last": [1, 5, 8, 11, 12, 13, 15], "least": [1, 2, 6, 11, 14, 15], "bsd": 1, "v3": 1, "licens": 1, "mean": [1, 2, 3, 5, 6, 8, 11, 12, 14], "read": [1, 4, 5, 6, 7, 13, 15], "studi": 1, "experi": [1, 11, 12], "yourself": [1, 2, 11, 13], "paragraph": [1, 5, 9, 11, 13], "preliminari": 1, "suggest": [1, 6, 11], "befor": [1, 2, 5, 6, 8, 9, 11, 12, 13], "order": [1, 2, 4, 5, 6, 7, 9, 11, 13, 14], "knowledg": 1, "book": [1, 6], "choos": [1, 11, 12], "what": [1, 4, 5, 6, 8, 11, 12, 14, 15, 16], "decor": [1, 4, 6, 14, 16], "mileston": 1, "fulli": [1, 4, 5, 6, 11], "follow": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "chapter": [1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14], "code": [1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "your": [1, 2, 3, 5, 6, 8, 9, 11, 12, 13, 14, 15, 16], "comput": [1, 16], "setup": [1, 3, 4, 12, 13, 15, 16], "plan": [1, 15], "do": [1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "safe": [1, 5, 6, 9, 13, 14], "run": [1, 3, 4, 5, 8, 12, 14, 16], "exampl": [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 16], "littl": [1, 5, 6], "strongli": [1, 5, 11, 13], "check": [1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14], "lint": 1, "visual": 1, "nowadai": 1, "multi": [1, 2, 6, 14], "choic": [1, 4, 11, 13], "studio": 1, "aka": 1, "jetbrain": 1, "deal": [1, 6, 15], "complex": [1, 2, 4, 5, 6, 8, 9, 12, 14, 15], "reliabl": 1, "virtual": [1, 16], "virtualenv": [1, 2], "introduct": [1, 2, 16], "mess": 1, "up": [1, 2, 5, 6, 8, 12, 15], "git": [1, 2], "keep": [1, 2, 4, 5, 6, 7, 8, 9, 11, 13, 14], "track": [1, 6, 14], "save": [1, 2, 4, 5, 6, 10, 11], "gitlat": 1, "bitbucket": 1, "editor": [1, 8], "highlight": [1, 8], "highli": [1, 13], "recommend": [1, 5, 6, 11], "quit": [1, 2, 5, 7, 8, 11, 13], "If": [1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "just": [1, 2, 4, 5, 6, 7, 8, 11, 13, 15], "folder": [1, 2, 3, 4, 5, 7, 10, 11, 13, 14], "add": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "arg": [1, 2, 6], "your_full_path_to_py4web": 1, "py": [1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15], "launch": [1, 2], "configur": [1, 2, 5, 6, 11, 12, 14], "note": [1, 2, 5, 8, 9, 11, 14, 15], "window": [1, 2, 4, 5, 6, 8, 11], "paramet": [1, 2, 4, 5, 12, 15], "must": [1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14], "forward": [1, 3], "slash": [1, 4, 5, 12], "onli": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "c": [1, 2, 3, 5, 6, 8, 9, 11, 14], "your_nam": [1, 15], "instead": [1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "copi": [1, 2, 3, 8, 9, 13, 16], "standard": [1, 2, 3, 6, 7, 12, 13, 14, 15, 16], "launcher": 1, "insid": [1, 2, 4, 5, 6, 8, 9, 11, 13, 14, 15], "renam": [1, 7], "error": [1, 2, 3, 5, 6, 7, 8, 9, 11, 14, 15], "later": [1, 2, 4, 5, 6, 8, 9, 11, 12, 13], "usr": 1, "bin": [1, 2], "env": [1, 14], "python3": [1, 2], "core": [1, 2, 4, 5, 11], "cli": [1, 2], "both": [1, 2, 4, 5, 6, 8, 11, 14], "get": [1, 2, 4, 5, 6, 8, 11, 12, 13, 14, 15, 16], "gevent": [1, 2], "true": [1, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "enabl": [1, 5, 6, 7, 9, 12, 14, 15], "build": [1, 2, 4, 5, 6, 9, 11, 13, 15], "execut": [1, 2, 5, 6, 8, 9, 11, 14, 15], "deploy": 1, "debugg": [1, 14], "support": [1, 4, 5, 8, 9, 10, 11, 12, 14, 15, 16], "effort": 1, "particip": 1, "try": [1, 2, 5, 6, 11, 12], "answer": 1, "submit": [1, 5, 6, 9, 11, 12, 13, 15], "bug": [1, 3, 6], "pull": [1, 6], "repositori": [1, 2, 3], "wish": [1, 6, 11, 15], "correct": [1, 6, 12], "expand": [1, 3], "translat": [1, 2, 4, 6, 8, 11, 14, 15, 16], "foreign": [1, 13], "inform": [1, 5, 6, 7, 11, 12, 14, 16], "directli": [1, 2, 4, 6, 7, 8, 9, 11, 12, 13, 15], "specif": [1, 2, 5, 6, 7, 8, 9, 11, 12, 14, 15], "readm": [1, 4, 11], "realli": [1, 4, 6], "rst": 1, "doc": 1, "browser": [1, 2, 3, 5, 7, 8, 13, 15], "onc": [1, 2, 5, 9, 11, 12, 13, 15], "pr": 1, "accept": [1, 2, 4, 5, 6, 9, 10, 11, 14, 15], "master": [1, 2, 6, 13], "branch": [1, 2], "reflect": [1, 6], "page": [1, 5, 6, 9, 11, 12, 13, 14, 15, 16], "epub": 1, "next": [1, 2, 5, 6, 8, 11], "output": [1, 2, 4, 5, 6, 7, 8, 9, 11, 15], "everyth": [2, 4, 14], "els": [2, 4, 6, 11, 12, 13, 14, 15], "charg": [2, 14], "reason": [2, 5, 6, 7, 13, 14], "thing": [2, 5, 6, 14], "py4web": [2, 3, 4, 5, 7, 8, 9, 11, 12, 13], "download": 2, "pypi": 2, "github": [2, 3, 4, 6, 13], "want": [2, 4, 5, 6, 8, 9, 11, 13, 15], "exist": [2, 5, 6, 11, 12, 13, 15], "scaffold": [2, 4, 5, 6, 8, 14, 15], "under": [2, 3, 4, 5, 6, 11], "process": [2, 4, 5, 6, 8, 11, 12, 13, 14, 15], "address": [2, 6, 11, 12], "each": [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "fine": [2, 12, 13], "maco": 2, "linux": 2, "7": [2, 7, 8, 11, 14], "advanc": [2, 12, 13, 16], "except": [2, 4, 5, 6, 9, 11, 12, 15], "four": [2, 6], "altern": [2, 5, 6, 8, 11, 15], "level": [2, 6, 7, 10, 11, 12], "difficulti": 2, "let": [2, 6, 7, 11, 15], "look": [2, 3, 5, 6, 7, 9, 11, 12], "pro": 2, "con": 2, "real": [2, 4, 7, 8, 11], "bunch": 2, "modifi": [2, 4, 6, 7, 8, 11, 13, 14, 15], "anyhow": 2, "simplest": [2, 4], "newbi": 2, "student": 2, "pre": [2, 4, 5, 6, 13], "nor": [2, 6, 11], "administr": 2, "On": [2, 6, 11, 12, 15], "hand": [2, 6, 11, 15], "experiment": [2, 5], "old": [2, 5, 11, 14], "difficult": [2, 15], "latest": [2, 6, 14], "extern": [2, 6], "unzip": 2, "With": [2, 6, 11, 12, 15], "type": [2, 4, 5, 7, 8, 9, 12, 13, 15], "rememb": [2, 3, 5, 6, 11], "document": [2, 3, 5, 6, 8, 9, 11, 15], "notic": [2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "correspond": [2, 3, 4, 6, 9, 10, 11, 12, 13, 14], "stabl": 2, "although": [2, 6, 8, 11], "date": [2, 6, 13], "quickli": [2, 11, 13], "dir": [2, 11, 15], "specifi": [2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "full": [2, 4, 6, 8, 11, 13, 15], "8": [2, 6, 7, 11], "path": [2, 4, 5, 6, 7, 10, 11, 13, 14, 15], "asset": 2, "after": [2, 3, 5, 6, 8, 11, 12, 13], "given": [2, 5, 6, 8, 11], "ex": 2, "point": [2, 3, 4, 5, 6, 7, 8, 11, 13, 14], "mistak": 2, "A": [2, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "sure": [2, 3, 4, 5, 6, 10, 11, 15], "prevent": [2, 5, 6, 8, 9, 11, 15], "unwant": [2, 6], "good": [2, 3, 11], "habit": 2, "still": [2, 5, 6, 9, 11, 13, 14], "discoveri": 2, "instruct": [2, 11], "activ": [2, 6, 12], "venv": 2, "tradit": [2, 6], "normal": [2, 4, 5, 6, 8, 9, 11, 13, 15], "util": [2, 4, 5, 8, 9, 11, 12, 13, 14, 16], "along": [2, 4, 9, 11, 15], "link": [2, 6, 7, 8, 9, 11, 12, 13, 15], "clone": [2, 4, 6, 15], "cd": 2, "content": [2, 4, 5, 6, 8, 9, 11, 13, 14, 15], "miss": [2, 12, 13], "manual": [2, 3, 4, 6, 9, 12, 13], "itself": [2, 6, 11, 15], "alreadi": [2, 3, 4, 5, 6, 9, 11, 12, 13], "present": [2, 6, 8, 11, 12], "gain": 2, "potenti": 2, "untest": [2, 12], "r": [2, 6, 11], "forc": [2, 6, 11, 12], "ones": [2, 5, 6, 7, 11, 14], "automat": [2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "safeti": 2, "precaut": 2, "backup": [2, 6, 11], "person": [2, 6, 7, 11, 13], "done": [2, 6, 8, 11, 13, 14], "delet": [2, 4, 7, 11, 13, 15], "again": [2, 3, 4, 5, 6, 11, 13, 15], "previou": [2, 5, 6, 8, 9, 11, 15], "name": [2, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], "wit": 2, "noth": [2, 5, 6, 11, 15], "expect": [2, 4, 5, 6, 9, 11, 14], "_dashboard": [2, 3], "_default": [2, 4], "describ": [2, 3, 4, 5, 6, 11], "welcom": [2, 4, 5, 8, 14, 15], "role": [2, 5, 9], "therefor": [2, 4, 5, 6, 8, 9, 11, 14], "actual": [2, 5, 6, 8, 13], "_": [2, 11, 15], "url": [2, 4, 5, 7, 8, 11, 12, 13, 14, 15], "localhost": [2, 4, 5, 6, 11], "8000": [2, 3, 4, 11, 13], "yourappnam": 2, "stop": [2, 3], "hit": [2, 6], "control": [2, 4, 5, 8, 9, 11, 12, 13, 14, 15], "appnam": [2, 5, 12, 14], "prefix": [2, 4, 6, 7, 8, 11, 14], "mai": [2, 4, 5, 6, 8, 9, 11, 13, 14, 15], "symlink": [2, 4], "trail": [2, 11], "ctrl": [2, 3], "break": [2, 6, 11], "fn": 2, "paus": 2, "argument": [2, 4, 5, 6, 8, 9, 11, 12, 14], "addit": [2, 4, 5, 6, 7, 8, 11, 13], "help": [2, 6, 7, 8, 9, 11, 14, 16], "h": [2, 11], "usag": [2, 3, 4, 5, 6, 9, 11, 12, 13], "apps_fold": 2, "func": [2, 5], "y": [2, 9, 11], "ye": [2, 6], "No": [2, 6], "prompt": [2, 4, 6], "assum": [2, 5, 6, 7, 10, 14], "fals": [2, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "text": [2, 5, 6, 8, 9, 13, 15], "pass": [2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "messag": [2, 4, 5, 7, 8, 11, 13, 14, 15], "exit": [2, 5], "myfunct": 2, "x": [2, 6, 8, 9, 11, 13], "100": [2, 6, 7, 8, 11], "doubl": [2, 6, 8], "quot": [2, 9], "shown": [2, 6, 11, 12], "app_nam": [2, 4, 5, 6], "scaffold_zip": 2, "current": [2, 5, 6, 8, 9, 12, 13, 14, 15], "127": [2, 3, 5, 11, 13], "0": [2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "1": [2, 3, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "p": [2, 8, 11], "integ": [2, 4, 6, 7, 9, 11, 15], "number": [2, 4, 5, 6, 8, 10, 11, 13], "password_fil": 2, "wsgiref": 2, "tornado": 2, "gunicorn": 2, "waitress": 2, "geventwebsocketserv": 2, "wsgirefthreadingserv": 2, "rocketserv": 2, "w": [2, 4, 5, 6], "number_work": 2, "worker": [2, 5], "d": [2, 7, 9, 11], "dashboard_mod": 2, "mode": [2, 4, 5, 11, 12], "readonli": [2, 11, 15], "none": [2, 5, 6, 7, 9, 11, 13, 14, 15], "watch": [2, 3, 16], "sync": [2, 6], "lazi": [2, 4, 14], "reload": [2, 3, 4, 5, 11, 15], "ssl_cert": 2, "ssl": 2, "certif": 2, "ssl_kei": 2, "kei": [2, 4, 5, 7, 9, 10, 11, 12, 16], "errorlog": 2, "send": [2, 6, 11, 12], "log": [2, 3, 4, 5, 6, 8, 9, 12, 14, 15], "stdout": 2, "stderr": 2, "tickets_onli": 2, "filenam": [2, 4, 5, 6, 11], "l": [2, 11], "logging_level": 2, "50": [2, 7], "30": [2, 6, 8, 11], "warn": [2, 5], "debug": [2, 4, 5, 6, 8], "switch": [2, 5, 6], "By": [2, 4, 5, 6, 11, 12, 14], "upon": [2, 8, 12, 14], "occur": [2, 6, 8, 11, 15], "incom": 2, "prefer": [2, 5, 6, 9, 15], "immedi": [2, 6, 13], "product": [2, 4, 6, 11, 15], "unned": 2, "restart": [2, 4, 5, 6, 13], "direct": [2, 5, 6, 8, 11], "modif": 2, "behaviour": [2, 6, 11, 13], "rocket3": [2, 14], "thread": [2, 5, 6, 14], "strip": [2, 11, 14], "python2": [2, 14], "logic": [2, 4, 5, 11, 12, 14], "valu": [2, 5, 7, 8, 9, 10, 11, 13, 15], "defin": [2, 4, 5, 8, 11, 12, 13, 14, 15], "common": [2, 3, 4, 5, 8, 11, 12, 14, 15], "notset": 2, "10": [2, 6, 7, 8, 9, 10, 11, 12, 15], "20": [2, 7, 10, 11], "info": [2, 4, 5, 6, 12], "40": [2, 9, 15], "critic": 2, "tell": [2, 4, 5, 6, 12, 13, 15], "handl": [2, 4, 5, 6, 11, 13, 14, 15], "event": [2, 6, 15], "found": [2, 6, 11], "invalid": [2, 5, 6, 11], "ask": [2, 5, 6], "annoi": 2, "pdkdf2": 2, "hash": [2, 6, 11], "unless": [2, 3, 5, 6, 8, 11], "my_password_fil": 2, "runtim": [2, 6], "valid": [2, 4, 7, 8, 9, 13, 14, 16], "crypt": 2, "write": [2, 4, 5, 6, 8, 11, 14], "str": [2, 5, 6, 9, 14, 15], "input": [2, 4, 5, 6, 8, 11, 15], "reinstal": 2, "confirm": [2, 11, 12, 13, 15], "creation": [2, 6], "parent": [2, 6, 8, 15], "myapp": [2, 4], "db": [2, 3, 4, 5, 7, 9, 11, 12, 13, 14, 15], "dal": [2, 4, 7, 11, 13, 15, 16], "field": [2, 4, 5, 7, 9, 12, 14, 15, 16], "too": [2, 5, 6, 11, 15], "cannot": [2, 5, 6, 8, 9, 11, 15], "deployment_tool": 2, "recip": 2, "briefli": 2, "tip": [2, 13, 16], "trick": 2, "To": [2, 4, 6, 8, 9, 10, 11, 12, 13], "step": [2, 6, 12, 14, 15], "www": [2, 8, 9, 13], "section": [2, 4, 6, 8, 11, 12], "io": [2, 6], "educ": 2, "brows": [2, 3, 13, 15], "vscode": 2, "updat": [2, 5, 11, 13, 14, 15, 16], "workspacefold": 2, "path_to": 2, "crt": [2, 6], "absolut": [2, 6, 14], "locat": [2, 4, 8, 14], "so": [2, 5, 6, 8, 9, 11, 13, 14, 15], "feasibl": [2, 11], "simpli": [2, 4, 5, 6, 8, 10, 15], "py4web_wsgi": 2, "4": [2, 5, 6, 7, 8, 11, 13], "take": [2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 15], "consol": [2, 9, 12, 15], "obtain": [2, 4, 6, 12, 15], "project_nam": 2, "mkdir": [2, 4, 7, 11, 13], "cp": 2, "development_tool": 2, "mayb": [2, 5], "empti": [2, 4, 6, 11, 15], "__init__": [2, 4, 5, 6, 7, 11, 13, 14, 15], "deploi": 2, "makefil": 2, "lib": [2, 8, 9, 13], "yaml": 2, "sdk": 2, "config": [2, 4], "email": [2, 5, 8, 11, 12, 14], "account": [2, 6], "suit": 2, "youtub": [2, 6], "video": [2, 15], "tutori": [2, 13, 15], "bottle_app": 2, "script": [2, 5, 8, 15], "dockerfil": 2, "compos": [2, 5], "yml": 2, "postgresql": [2, 6], "advantag": [2, 6, 8, 15], "sudo": [2, 12], "background": [2, 4, 11, 15], "daemon": 2, "bash": 2, "04": 2, "03": [2, 6, 7], "lt": [2, 9], "nginx": 2, "self": [2, 5, 9, 11, 13, 15], "sign": [2, 4, 5, 6, 8, 11, 12], "iptabl": 2, "extens": [3, 6, 11, 14], "explor": 3, "listen": 3, "tcp": 3, "local": [3, 5, 6, 11, 12, 13, 14], "pc": 3, "protocol": 3, "connect": [3, 4, 5, 11, 14], "firefox": [3, 15], "chrome": [3, 15], "button": [3, 4, 5, 9, 11, 15, 16], "20201112": 3, "sourc": [3, 4, 6, 11], "forum": 3, "press": [3, 4, 13], "insert": [3, 5, 7, 8, 11, 12, 13, 15], "set_password": 3, "command": [3, 4, 8, 9, 16], "displai": [3, 4, 5, 6, 8, 9, 11, 12, 13, 15], "tab": [3, 5, 11], "compress": [3, 4], "click": [3, 6, 9, 11, 13, 15], "titl": [3, 8, 11, 15], "context": [3, 5, 6, 8], "select": [3, 4, 7, 10, 11, 12, 13, 14, 15, 16], "compris": 3, "effect": [3, 6, 11], "fail": [3, 6, 8, 11], "load": [3, 4, 5, 6, 8, 13, 15], "red": [3, 4, 6, 8, 9, 11], "crud": [3, 11, 15], "visit": [3, 5, 6, 8, 11, 12], "trigger": [3, 5, 6, 15], "recent": [3, 5, 6, 11, 13], "own": [4, 5, 6, 8, 11, 12, 13, 14, 15], "strictli": [4, 15], "enter": [4, 5, 9, 11, 12], "echo": 4, "backslash": 4, "e": [4, 5, 6, 7, 8, 9, 11, 12, 15], "recogn": [4, 6], "whenev": [4, 6], "anyth": 4, "arbitrari": [4, 6, 12], "typic": [4, 5, 6, 8, 11], "subfold": [4, 5, 6], "publish": 4, "hello": [4, 5, 6, 8, 9, 11, 12, 15], "world": [4, 5, 6, 8, 9, 11], "newli": [4, 6, 10], "One": [4, 11, 12], "__": 4, "stream": [4, 5, 6, 14], "partial": [4, 6, 14], "rang": [4, 6, 8, 12, 14], "header": [4, 5, 6, 8, 9, 13, 15], "datetim": [4, 5, 6, 7, 11, 15], "action": [4, 5, 6, 9, 11, 14, 15, 16], "def": [4, 5, 6, 7, 9, 11, 12, 13, 14, 15], "webserv": 4, "prepend": [4, 7, 11, 14, 15], "ambigu": [4, 6], "string": [4, 5, 7, 8, 9, 10, 11, 13], "dictionari": [4, 5, 9, 10, 14], "serial": [4, 5, 6, 8, 9, 11], "end": [4, 6, 8, 9, 11, 12, 13, 14, 15], "color": [4, 6, 8, 9, 11, 15], "blue": [4, 6, 11, 14], "green": [4, 6, 14], "visibl": 4, "chose": 4, "convent": [4, 6, 8, 11, 14], "often": [4, 6, 12, 15], "shortli": 4, "possibl": [4, 5, 6, 8, 11, 13, 14], "map": [4, 6, 9, 10, 11, 14], "pattern": [4, 7], "pick": [4, 5, 12], "unknown": 4, "wildcard": 4, "filter": [4, 7, 11, 12], "int": [4, 6, 11, 14], "match": [4, 5, 6, 7, 8, 9, 10, 11], "digit": [4, 6, 11, 12], "float": [4, 11], "decim": [4, 6, 11], "charact": [4, 6, 9, 11], "non": [4, 5, 6, 11, 13, 14], "greedi": 4, "segment": 4, "exp": [4, 6], "express": [4, 7, 8, 9, 10, 11, 13, 15], "variabl": [4, 5, 6, 9, 10, 11, 13, 15], "method": [4, 5, 7, 8, 9, 11, 12, 13, 15, 16], "post": [4, 6, 7, 9, 11, 12, 13, 14, 15], "paint": [4, 11], "queri": [4, 5, 7, 9, 11, 12, 13, 14, 15], "equival": [4, 5, 6, 9, 11, 14], "attribut": [4, 5, 9, 11, 13, 14, 15], "identifi": [4, 5, 6, 9, 11], "head": [4, 8, 11, 13], "style": [4, 8, 11, 15, 16], "bodi": [4, 8, 12, 13, 15], "h1": [4, 8], "dict": [4, 5, 6, 10, 11, 13, 14, 15], "ingredi": 4, "behavior": [4, 6, 9, 11, 14, 15], "scope": 4, "plugin": [4, 5, 11, 13, 15], "tree": [4, 8, 11], "explain": [4, 5, 6, 11, 13], "That": [4, 6, 14], "easili": [4, 5, 6, 8, 9, 11, 13], "pool": [4, 5], "commit": [4, 5, 7, 13, 14], "rollback": 4, "failur": [4, 11, 15], "pars": [4, 5, 6, 10, 11, 12], "retriev": [4, 5, 6, 14, 15], "determin": [4, 5, 6, 8, 10, 11, 12, 13], "optim": [4, 6], "rule": [4, 5, 8, 11, 13], "sane": 4, "outlin": [4, 5], "put": [4, 5, 7, 11, 15], "organ": 4, "properli": 4, "registr": [4, 11], "construct": [4, 6], "metadata": [4, 6], "md": 4, "tabl": [4, 5, 7, 11, 12, 13, 14, 15], "settings_priv": 4, "privat": [4, 5, 11], "ship": [4, 8], "bulma": [4, 11, 13, 15], "agnost": 4, "past": 4, "favicon": 4, "ico": 4, "etc": [4, 5, 6, 7, 10, 14], "layout": [4, 11, 12, 13, 14, 15, 16], "italian": [4, 5, 10], "respons": [4, 5, 6, 8, 13, 14, 15, 16], "abort": [4, 6], "redirect": [4, 5, 11, 12, 13, 15], "get_us": [4, 5, 12, 14], "first_nam": [4, 5, 12, 13], "format": [4, 5, 7, 9, 10, 12, 13], "bottlepi": [4, 5], "counterpart": 4, "div": [4, 5, 8, 11, 14, 15], "span": [4, 14], "img": 4, "pretti": [4, 6, 11, 14], "indic": [4, 6, 11, 14, 15], "probabl": [4, 11], "line": [4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16], "whole": [4, 11], "anoth": [4, 5, 8, 10, 11], "my_app": 4, "Then": [4, 5, 6, 9, 11, 12, 13, 15], "upper": [4, 5, 11], "As": [4, 5, 6, 7, 8, 9, 11, 12, 13, 15], "facilit": 4, "fact": [4, 5, 6, 14], "handler": 4, "app_watch_handl": 4, "report": [4, 8], "worri": [4, 6], "sass": 4, "compil": [4, 8, 9, 11], "libsass": 4, "static_dev": 4, "overrid": [4, 6, 8, 9, 12, 13, 14, 15], "sass_compil": 4, "changed_fil": 4, "print": [4, 6, 9, 10, 12, 14], "abov": [4, 5, 6, 7, 8, 11, 12, 13], "compiled_css": 4, "filep": 4, "include_path": 4, "output_styl": 4, "dest": [4, 6], "o": [4, 5, 7, 11, 13], "join": [4, 5, 7, 11, 12, 13, 14, 16], "esprima": 4, "implement": [4, 5, 6, 8, 11, 12, 14, 15], "node": 4, "dbadmin": 4, "validate_j": 4, "cf": 4, "abspath": 4, "parsemodul": 4, "filepath": 4, "rel": [4, 6, 8, 13, 14], "ignor": [4, 6, 8, 9, 11, 13, 15], "termin": [4, 8, 15], "equip": 5, "furnitur": 5, "fix": [5, 14], "posit": [5, 9], "vehicl": 5, "someth": [5, 6, 7, 8, 9, 11], "attach": [5, 6, 12, 14], "transact": 5, "lookup": [5, 7, 11], "proper": [5, 6, 13], "back": [5, 6, 11], "accomplish": [5, 6, 12, 14], "skip": [5, 15], "manner": [5, 14], "boilerpl": 5, "wsgi": 5, "middlewar": 5, "authent": [5, 6, 13, 16], "return": [5, 6, 7, 9, 11, 12, 13, 15], "otherwis": [5, 6, 8, 11, 12, 13], "variou": [5, 6], "transform": [5, 6, 11, 15], "jinja2": 5, "syntact": [5, 11], "sugar": 5, "ram": [5, 6], "Be": [5, 6, 8], "care": [5, 6, 8, 14, 15], "exactli": [5, 6, 8], "opposit": [5, 6], "earli": [5, 6], "februari": 5, "2022": 5, "combin": [5, 9, 11, 13], "implic": 5, "cleaner": 5, "my_var": [5, 9], "t_folder": 5, "dirnam": [5, 7, 11, 13], "__file__": [5, 7, 11, 13], "class": [5, 6, 8, 9, 11, 12, 14, 15], "counter": [5, 6, 15], "count": [5, 7, 12, 13, 15], "dbstore": 5, "memori": 5, "storag": [5, 6, 7, 11, 13], "n": [5, 6, 10, 15], "en": [5, 7], "2": [5, 6, 7, 8, 9, 10, 11, 12, 14, 15], "twice": [5, 6], "6": [5, 6, 7, 11, 12, 14], "5": [5, 6, 7, 8, 10, 11, 12, 13, 15], "english": 5, "ti": 5, "ho": 5, "visto": 5, "prima": 5, "gia": 5, "volt": 5, "piu": 5, "alert": [5, 8, 9, 11, 14], "state": [5, 6, 7, 14, 15], "dismiss": [5, 14], "_class": [5, 9, 11, 15], "sanit": [5, 9, 14], "src": [5, 6, 8, 9, 15], "xml": [5, 8, 11, 14, 15], "achiev": [5, 6, 14], "temporarili": 5, "sent": [5, 6, 12, 15], "overwritten": [5, 14], "hardcod": 5, "speak": 5, "desir": [5, 8, 11, 12], "persist": [5, 6, 12], "throughout": 5, "word": [5, 6, 10, 13], "render": [5, 7, 8, 9, 11, 13, 14, 15], "stateless": [5, 15], "secret": [5, 12], "my": [5, 8, 9, 11, 12, 15], "increas": 5, "close": [5, 6, 8, 9], "reopen": 5, "relat": [5, 16], "usernam": [5, 6, 12], "shop": 5, "cart": 5, "jwt": 5, "token": [5, 11], "__str__": [5, 8, 9], "minim": [5, 13, 16], "never": [5, 6, 8, 11, 14, 15], "expir": [5, 6], "3600": [5, 6], "algorithm": [5, 11], "hs256": 5, "same_sit": 5, "lax": 5, "_sesson": 5, "passphras": 5, "maximum": [5, 6, 11], "lifetim": 5, "second": [5, 9, 11, 12, 14], "timeout": 5, "signatur": [5, 7, 9, 11], "csrf": [5, 11], "attack": [5, 9], "cross": [5, 6, 9], "forgeri": 5, "uuid": [5, 6], "why": [5, 6, 8, 14], "appname_sess": 5, "encod": [5, 6, 11, 15], "tamper": [5, 6], "trivial": [5, 6], "disk": [5, 6], "sensit": [5, 6, 11], "vice": 5, "versa": 5, "small": [5, 11], "size": [5, 6, 8, 11], "kbyte": 5, "conn": 5, "11211": 5, "6379": 5, "lambda": [5, 6, 9, 11, 13, 15], "k": [5, 8, 14], "v": [5, 9, 11, 15], "ct": 5, "ttl": 5, "monkei": 5, "patch": 5, "multiprocess": 5, "quirk": 5, "determinist": 5, "unsaf": [5, 9], "imagin": [5, 6, 8, 15], "fsstorag": 5, "fp": 5, "dump": [5, 6, 15], "tmp": [5, 6], "leav": [5, 6, 12], "exercis": [5, 12], "lock": [5, 6], "ineffici": [5, 6], "scale": [5, 8], "app1": 5, "app2": 5, "sesson": 5, "session_secret_kei": 5, "app1_sess": 5, "between": [5, 6, 9, 11, 14], "consist": [5, 6, 8, 13, 14], "session_app1": 5, "restrict": [5, 6, 7, 11, 15], "enforc": [5, 6, 11, 14], "workflow": [5, 16], "step1": 5, "step_complet": 5, "_href": [5, 8, 9, 11, 13], "step2": 5, "step3": 5, "on_request": 5, "evalu": [5, 6, 7, 8, 11, 15], "rais": [5, 6, 11, 14], "404": [5, 11], "cond": 5, "400": [5, 14], "on_fals": 5, "13": [5, 6, 11], "auth_us": [5, 6, 12], "requires_membership": 5, "group_nam": [5, 12], "user_id": [5, 6, 14], "payrol": 5, "employe": [5, 13], "signer": 5, "url_sign": 5, "somepath": 5, "signed_url": 5, "anotherpath": 5, "verifi": [5, 11, 12], "abstract": [5, 14, 16], "layer": [5, 14, 16], "pleas": [5, 6], "doesn": [5, 6, 13], "db_folder": [5, 7, 11, 13], "pool_siz": [5, 6], "define_t": [5, 7, 11, 12, 13, 14], "visit_log": 5, "client_ip": 5, "timestamp": [5, 7, 15], "remote_addr": [5, 12], "utcnow": [5, 6], "wrap": [5, 10, 11], "on_success": 5, "roll": [5, 6], "on_error": 5, "Their": [5, 6, 14], "constructor": [5, 9, 12, 16], "last_nam": [5, 12, 13], "sso_id": [5, 12], "action_token": [5, 12], "mostli": 5, "presenc": [5, 6], "_scaffold": [5, 8, 9, 11, 12, 13, 14, 16], "author": [5, 6, 7, 16], "writabl": [5, 6, 11, 14], "readabl": [5, 6, 7, 13, 14], "threadsafevari": 5, "myfixtur": 5, "eventu": 5, "inner": [5, 8, 15], "b": [5, 6, 8, 9, 11, 14, 15], "circumst": 5, "think": [5, 6, 8, 13, 15], "onion": 5, "center": [5, 8], "outer": [5, 11, 15], "previous": [5, 6, 11], "uppercas": [5, 11], "upper_cas": 5, "traceback": [5, 6], "logerror": 5, "errlog": 5, "myerror": 5, "__prerequisite__": 5, "append": [5, 6, 9, 11, 13, 15], "__prerequisites__": 5, "guarante": [5, 6, 8, 14], "prerequisit": [5, 16], "singleton": [5, 14], "consid": [5, 6, 8, 9, 11, 13, 14], "httrespons": 5, "complic": 5, "whether": [5, 6, 8, 11, 12, 13, 14], "mandatori": [5, 6, 8], "happen": [5, 6], "sequenc": [5, 6], "revers": [5, 6], "extra": [5, 6, 11, 12], "almost": 5, "futur": [5, 14, 15], "lru": 5, "via": [5, 6, 8, 9, 12, 15], "1000": [5, 6, 11, 15], "60": [5, 6], "uuid4": [5, 6], "mention": [5, 12, 15], "unauthent": [5, 6, 9], "below": [5, 6, 7, 11, 13], "separ": [5, 6, 8, 11, 12, 13, 15], "preced": [5, 6, 7, 8, 11], "dynam": [6, 7, 8, 9, 11, 12, 16], "dialect": 6, "term": [6, 11], "portabl": 6, "among": [6, 11], "choosen": 6, "pure": 6, "conceiv": 6, "tast": 6, "aggreg": 6, "nest": [6, 8, 9], "caveat": [6, 8, 14, 16], "startup": [6, 14, 16], "downsid": [6, 14], "approach": [6, 14], "modern": [6, 12, 15], "driver": 6, "sqlite3": 6, "mac": 6, "binari": 6, "appropri": [6, 11], "pysqlite2": 6, "zxjdbc": 6, "jython": 6, "psycopg2": 6, "pymysql": 6, "mysqldb": 6, "cx_oracl": 6, "pyodbc": 6, "pypyodbc": 6, "firebird": 6, "kinterbasdb": 6, "fdb": 6, "db2": 6, "informix": 6, "informixdb": 6, "ingr": 6, "ingresdbi": 6, "cubrid": 6, "cubriddb": 6, "sybas": 6, "teradata": 6, "sapdb": 6, "mongodb": 6, "pymongo": 6, "imap": 6, "imaplib": 6, "treat": [6, 14], "instanti": [6, 12, 13, 14], "mytabl": 6, "myfield": 6, "truncat": 6, "import_from_csv_fil": 6, "claus": [6, 8], "myqueri": 6, "myset": 6, "somevalu": 6, "deriv": 6, "myorder": 6, "advis": [6, 13], "NOT": [6, 11, 12], "hesit": 6, "snippet": 6, "auth_user_tag_group": [6, 12], "superhero": [6, 7, 11, 13, 15], "superpow": [6, 7], "superman": [6, 7, 11, 13], "real_ident": [6, 7], "zero": [6, 8, 11, 13], "sake": 6, "simplic": [6, 15], "engin": [6, 11], "At": [6, 8], "_uri": 6, "_dbname": 6, "instanc": [6, 9, 11, 13, 15], "uniform": 6, "resourc": [6, 12, 16], "situat": 6, "dummi": [6, 8], "db_codec": 6, "utf": 6, "check_reserv": 6, "migrate_en": 6, "fake_migrate_al": 6, "decode_credenti": 6, "driver_arg": 6, "adapter_arg": 6, "auto_import": 6, "bigint_id": 6, "lazy_t": 6, "db_uid": 6, "do_connect": 6, "after_connect": 6, "ignore_field_cas": 6, "entity_quot": 6, "table_hash": 6, "establish": [6, 14], "set_encod": 6, "utf8mb4": 6, "postgr": 6, "2005": 6, "mssql3": 6, "2012": 6, "mssql4": 6, "dsn": 6, "uid": 6, "pwd": 6, "ndb": 6, "alter": 6, "utf8": 6, "unicod": [6, 11], "byte": [6, 11], "buffer": 6, "had": 6, "_select": [6, 9], "_insert": 6, "_updat": 6, "_delet": 6, "behav": [6, 13], "latin1": 6, "unicodedecodeerror": 6, "rather": [6, 8, 9, 11, 15], "slow": 6, "goe": [6, 11], "recycl": 6, "minimum": [6, 11], "receiv": 6, "share": [6, 14], "sequenti": 6, "simultan": 6, "yield": 6, "benefit": [6, 15], "wait": 6, "stai": 6, "unus": 6, "retri": 6, "major": 6, "boost": [6, 8], "defer": [6, 15], "referenc": [6, 7, 9], "possibli": 6, "howev": [6, 8, 9, 11, 13, 15], "demand": 6, "housekeep": 6, "definit": [6, 7, 11, 13, 14], "maintain": [6, 8, 13, 15], "workload": 6, "slave": 6, "column": [6, 16], "against": [6, 7, 9, 11, 13], "target": [6, 9, 15], "scan": 6, "known": [6, 8], "_nonreserv": 6, "postgres_nonreserv": 6, "backend": 6, "entiti": 6, "unquot": [6, 9], "insensit": [6, 11], "thu": [6, 8], "fold": 6, "accord": [6, 8], "compliant": [6, 15], "schema": 6, "arrang": [6, 11], "table1": 6, "sometim": [6, 8, 9, 11, 13], "necessari": [6, 8, 9], "user_nam": 6, "user_password": 6, "server_addr": 6, "db_name": 6, "sslmode": 6, "sslrootcert": 6, "root": [6, 8, 12, 13], "sslcert": 6, "sslkei": 6, "boolean": [6, 13], "affect": 6, "disabl": [6, 7, 9, 11, 13], "fake": 6, "aren": 6, "ever": 6, "granular": 6, "bob": 6, "enclos": [6, 8], "pseudo": 6, "visitor": [6, 9, 11], "tablenam": [6, 7, 11, 15], "kwarg": [6, 15], "subclass": [6, 11], "common_filt": 6, "catch": 6, "anywai": 6, "auto": [6, 13], "increment": 6, "uniqu": [6, 7, 11], "element": [6, 8, 9, 11, 13, 15], "redefinit": 6, "anonym": [6, 8, 11], "down": [6, 11, 15], "othert": 6, "otherfield": 6, "alia": [6, 11], "illustr": [6, 8], "qualifi": 6, "db1": 6, "dbo": 6, "part": [6, 9, 11, 14], "sub": 6, "relev": 6, "numer": [6, 11], "lose": [6, 11], "delai": [6, 15], "ag": 6, "set_attribut": 6, "is_not_empti": 6, "is_int_in_rang": 6, "120": 6, "is_in_db": [6, 13], "somet": 6, "somefield": 6, "some_valu": 6, "caus": [6, 11], "underscor": [6, 9, 11], "_extra": 6, "condit": [6, 8, 11, 16], "easiest": [6, 12], "met": 6, "accnum": 6, "acctyp": 6, "accdesc": 6, "null": [6, 7, 11, 15], "fieldnam": [6, 15], "length": [6, 11], "ondelet": 6, "notnul": 6, "uploadfield": 6, "widget": [6, 16], "label": [6, 7, 8, 11, 13, 14, 15], "comment": [6, 9], "searchabl": 6, "listabl": 6, "autodelet": 6, "uploadfold": 6, "uploadsepar": 6, "uploadf": 6, "custom_qualifi": 6, "map_non": 6, "Not": 6, "upgrad": [6, 16], "popul": 6, "seem": 6, "redund": [6, 11], "ON": 6, "statement": [6, 8, 11], "NO": 6, "somewher": 6, "blob": 6, "upload_fold": [6, 11], "attent": 6, "either": [6, 9, 11], "move": 6, "amazon": 6, "s3": 6, "sftp": 6, "pyfilesystem": 6, "due": [6, 7, 11], "associ": [6, 10, 11, 12], "autogener": 6, "neither": [6, 11], "reset": 6, "is_length": 6, "512": 6, "32768": 6, "31": [6, 7, 11], "gib": 6, "is_float_in_rang": 6, "1e100": 6, "is_decimal_in_rang": 6, "is_dat": 6, "is_tim": 6, "is_datetim": 6, "_id": [6, 9, 11, 15], "is_empty_or": 6, "is_json": 6, "bigint": 6, "63": [6, 11], "big": [6, 14], "total": [6, 7], "respect": [6, 9, 11, 13], "certain": [6, 8, 11, 13], "denorm": [6, 7], "listproperti": 6, "stringlistproperti": 6, "item": [6, 7, 8, 9, 11], "escap": [6, 8, 9], "explanatori": 6, "backport": 6, "base64": [6, 8, 15], "decod": 6, "extract": [6, 11, 15], "neg": [6, 11], "33": [6, 11], "space": [6, 8, 11, 13], "_format": 6, "similarli": [6, 9], "32": [6, 11], "_tabl": [6, 11, 15], "_tablenam": 6, "_db": 6, "tupl": [6, 8, 9], "myfil": 6, "imag": [6, 8, 9, 11], "assign": [6, 8, 11, 12, 14], "duplic": 6, "file_cont": [6, 15], "file_nam": [6, 15], "occasion": [6, 11], "programmat": [6, 9, 11], "rb": 6, "simpler": [6, 8, 13], "temp": 6, "image_fil": 6, "plain": 6, "fullnam": 6, "nameonli": 6, "origin": [6, 8, 9, 10, 11], "readi": 6, "contextlib": 6, "shutil": 6, "wb": 6, "copyfileobj": 6, "logfil": 6, "unnam": [6, 9], "recov": 6, "junk": 6, "complain": 6, "corrupt": 6, "smaller": 6, "involv": [6, 8, 13, 14], "confus": 6, "rebuild": 6, "prudent": 6, "yourapp": 6, "narrow": 6, "summar": 6, "alex": 6, "ident": [6, 7, 11, 14], "bulk_insert": 6, "tim": 6, "oppos": [6, 9], "loop": [6, 8], "speed": [6, 7, 8], "carl": 6, "q": [6, 13], "far": [6, 15], "birthplac": 6, "chicago": 6, "born": 6, "hi": [6, 11], "criteria": [6, 11], "pet": 6, "rover": 6, "ret": 6, "bail": 6, "hold": [6, 15], "whose": [6, 8, 11], "rare": 6, "num": 6, "properti": [6, 11], "id1": 6, "chair": 6, "id2": 6, "materi": 6, "wood": 6, "assert": 6, "thing_tags_default": 6, "tail": 6, "u": [6, 9, 11], "five": 6, "placehold": [6, 10, 11, 15], "colnam": 6, "as_ordered_dict": 6, "substitut": [6, 8], "cursor": [6, 15], "field1": 6, "val1_row1": 6, "field2": 6, "val2_row1": 6, "val1_row2": 6, "val2_row2": 6, "former": 6, "ensur": [6, 11], "ordereddict": 6, "_time": 6, "took": 6, "recurr": 6, "IF": 6, "myidx": 6, "INTO": 6, "_count": 6, "susan": 6, "moreov": [6, 15], "act": [6, 9, 11], "latter": [6, 11, 14], "over": [6, 8, 13, 15], "particularli": 6, "handi": 6, "compact": 6, "notat": [6, 9], "unusu": 6, "delete_record": 6, "feed": 6, "entir": [6, 8, 14, 15], "larg": [6, 11], "dramat": 6, "iterselect": 6, "machin": 6, "rewrit": 6, "repr_row": 6, "slice": 6, "wouldn": 6, "myrecord": 6, "del": [6, 9], "19": [6, 7, 11], "02": [6, 11], "permit": 6, "conveni": [6, 16], "appar": 6, "safer": [6, 11, 13], "meet": 6, "owner_id": 6, "nevertheless": 6, "tild": 6, "appear": [6, 11, 13], "random": [6, 8, 11, 12, 15], "overcom": 6, "concaten": [6, 9], "conjunct": 6, "condition": 6, "query1": 6, "query2": 6, "offset": [6, 7], "implicitli": [6, 11], "pagin": [6, 13], "trade": 6, "AND": 6, "OR": [6, 9, 11, 13], "negat": 6, "invert": [6, 11], "unari": 6, "overload": 6, "higher": 6, "comparison": [6, 11], "parenthes": 6, "william": 6, "ken": 6, "yes_or_no": 6, "curt": 6, "philip": 6, "modified_on": 6, "retain": 6, "first_row": 6, "last_row": 6, "obvious": [6, 11], "won": 6, "forget": [6, 13], "table_nam": 6, "optimis": 6, "rows_list": 6, "first_row_dict": 6, "themselv": [6, 8], "rows1": 6, "rows2": 6, "union": 6, "rows3": 6, "intersect": 6, "pointless": 6, "manipul": 6, "unchang": [6, 11], "obviou": [6, 7, 8, 12], "cache_db_select": 6, "lack": [6, 11, 14], "unit_pric": 6, "quantiti": [6, 11], "total_pric": 6, "rid": 6, "99": 6, "9": [6, 7, 11, 13, 14], "95": 6, "1l": 6, "wiki": [6, 7], "subsect": 6, "mark": [6, 12], "calcul": 6, "discounted_tot": 6, "discount": 6, "implicit": 6, "price": 6, "percentag": 6, "sai": [6, 12], "15": [6, 11, 12, 13], "myvirtualfield": 6, "virtualfield": 6, "order_item": 6, "setvirtualfield": 6, "myvirtualfields1": 6, "discounted_unit_pric": 6, "90": [6, 7], "myvirtualfields2": 6, "discounted_total_pric": 6, "lazy_total_pric": 6, "shorter": [6, 11], "owner": [6, 11], "intend": [6, 15], "cyclic": 6, "boat": 6, "shoe": 6, "acquir": 6, "transpar": [6, 8, 11], "observ": 6, "owner_id1": 6, "owner_id2": 6, "with_alia": 6, "he": 6, "clearli": [6, 15], "equal": [6, 7, 9, 12, 15], "realiz": 6, "intermedi": 6, "ownership": 6, "relationship": 6, "rewritten": 6, "co": [6, 11], "persons_and_th": 6, "lighter": [6, 14], "father_id": 6, "mother_id": 6, "father": 6, "temporari": 6, "fid": 6, "mid": 6, "claudia": 6, "marco": 6, "mother": 6, "AS": 6, "chosen": 6, "subtl": 6, "clear": [6, 11], "event_tim": 6, "sever": 6, "few": [6, 8, 12, 15], "xss": [6, 8, 9], "inject": [6, 8, 11, 16], "unauthor": 6, "percent": 6, "wild": 6, "card": 6, "ansi": 6, "collat": 6, "case_sensit": 6, "roughli": 6, "value1": 6, "value2": 6, "degre": 6, "2018": 6, "IN": 6, "bad_dai": 6, "jonathan": 6, "verbos": 6, "nested_select": 6, "averag": 6, "plu": [6, 8, 12, 13], "43": 6, "sysus": 6, "power": [6, 7, 12, 14, 15], "coa": 6, "mathemat": 6, "rescu": 6, "dumpfil": 6, "newlin": [6, 11], "export_to_csv_fil": 6, "explict": 6, "restor": 6, "somefil": 6, "field3": 6, "suffic": 6, "nid": 6, "across": [6, 8], "merg": 6, "64": [6, 8, 15], "stringio": 6, "set_head": 6, "getvalu": 6, "import_and_sync": 6, "_type": [6, 9, 11, 15], "_name": [6, 9, 11, 15], "var": [6, 9, 11, 14, 15], "rpc": 6, "therein": 6, "thead": 6, "tr": 6, "th": 6, "tbodi": 6, "w2p_odd": 6, "odd": [6, 8], "td": 6, "w2p_even": 6, "univers": [6, 9, 11], "f": [6, 12, 13, 14, 15], "parser": 6, "quotechar": 6, "quote_minim": 6, "oufil": 6, "quote_nonnumer": 6, "35": 6, "descript": [6, 7, 11, 12, 13], "2013": 6, "consult": 6, "is_in_set": 6, "toi": 6, "car": 6, "comma": [6, 15], "suffici": 6, "gender": 6, "doctor": 6, "reus": 6, "is_act": 6, "created_on": 6, "created_bi": 6, "modified_bi": 6, "payment": 6, "amount": 6, "anyobj": 6, "obj": [6, 8], "myobj": 6, "aid": 6, "myobjnam": 6, "sqlcustomtyp": 6, "six": 6, "_before_insert": 6, "_after_insert": 6, "_before_upd": 6, "_after_upd": 6, "_before_delet": 6, "_after_delet": 6, "pprint": 6, "before_insert": 6, "after_insert": 6, "oprow": 6, "before_upd": 6, "after_upd": 6, "before_delet": 6, "after_delet": 6, "pair": [6, 9], "_before_": 6, "fire": 6, "infinit": 6, "update_na": 6, "consequ": [6, 11, 15], "enable_record_vers": 6, "stored_item": 6, "hidden": [6, 11, 15], "_enable_record_vers": 6, "archive_db": 6, "archive_nam": 6, "stored_item_arch": 6, "current_record": 6, "archiv": 6, "hide": [6, 13], "tenanc": 6, "idea": 6, "repeat": [6, 11], "blog_post": 6, "subject": [6, 7, 12], "post_text": 6, "is_publ": 6, "public": 6, "_common_filt": 6, "phrase": 6, "enhanc": 6, "disallow": [6, 11], "ignore_common_filt": [6, 11], "asid": 6, "suppos": 6, "ip": [6, 11], "ip2int": 6, "sv": 6, "ipv4": [6, 11], "sp": 6, "split": [6, 7, 8, 11], "iip": 6, "int2ip": 6, "iv": 6, "ov": 6, "divmod": 6, "256": 6, "nativ": 6, "websit": [6, 15], "ipaddr": 6, "wikipedia": [6, 7], "91": 6, "198": 6, "174": 6, "192": [6, 11], "172": [6, 11], "217": 6, "11": [6, 11], "74": 6, "125": 6, "65": 6, "207": 6, "97": 6, "227": 6, "239": 6, "factori": [6, 9], "concern": 6, "awai": 6, "phase": 6, "db_a": 6, "db_b": 6, "distributed_transaction_commit": 6, "succe": 6, "mydb": 6, "2010": 6, "connectionpool": 6, "baseadapt": 6, "deleg": 6, "myvalu": 6, "_adapt": 6, "_listifi": 6, "list_of_field": 6, "moment": 6, "sqliteadapt": 6, "jdbcsqliteadapt": 6, "mysqladapt": 6, "postgresqladapt": 6, "jdbcpostgresqladapt": 6, "oracleadapt": 6, "mssqladapt": 6, "mssql2adapt": 6, "mssql3adapt": 6, "mssql4adapt": 6, "firebirdadapt": 6, "firebirdembeddedadapt": 6, "informixadapt": 6, "db2adapt": 6, "ingresadapt": 6, "ingresunicodeadapt": 6, "googlesqladapt": 6, "nosqladapt": 6, "googledatastoreadapt": 6, "cubridadapt": 6, "teradataadapt": 6, "sapdbadapt": 6, "couchdbadapt": 6, "imapadapt": 6, "mongodbadapt": 6, "verticaadapt": 6, "sybaseadapt": 6, "char": [6, 12], "varchar": 6, "longtext": 6, "credential_decod": 6, "pool_connect": 6, "foreign_key_check": 6, "sql_mode": 6, "no_backslash_escap": 6, "lastrowid": 6, "last_insert_id": 6, "fetchon": 6, "couchdb": 6, "ibm_db_dbi": 6, "db2ibm": 6, "db2pyodbc": 6, "firebird_embed": 6, "firebirdembed": 6, "googlemysql": 6, "googledatastor": 6, "googlepostgr": 6, "googlesql": 6, "se": 6, "informixs": 6, "ingresu": 6, "ingresunicod": 6, "jdbc": 6, "jdbcpostgr": 6, "jdbcsqlite": 6, "mongo": 6, "mssql1": 6, "mssql2": 6, "mssql1n": 6, "mssql3n": 6, "mssql4n": 6, "mssqln": 6, "postgres2": 6, "postgrenew": 6, "postgrepsyconew": 6, "postgres3": 6, "postgreboolean": 6, "postgrepsycoboolean": 6, "postgrepsyco": 6, "pytd": 6, "sap": 6, "spatialit": 6, "vertica": 6, "sqladapt": 6, "mysqldv": 6, "invis": 6, "decid": [6, 11, 13], "reinstat": 6, "rebuilt": 6, "awar": 6, "continu": [6, 8, 15], "unfortun": [6, 13], "aftermath": 6, "revert": 6, "py4web_filesystem": 6, "discard": 6, "consider": 6, "overhead": 6, "resultset": 6, "superseed": 6, "onward": 6, "deprec": [6, 11, 15], "circular": 6, "on_delete_act": 6, "run_in_transact": 6, "liststringproperti": 6, "rest": [7, 12], "cit0801": 7, "inspir": 7, "graphql": 7, "cit0802": 7, "less": [7, 11, 14], "spirit": 7, "impli": 7, "might": [7, 11], "isdir": [7, 11, 13], "job": [7, 11, 13], "strength": 7, "clark": [7, 13], "kent": [7, 13], "journalist": [7, 13], "peter": [7, 13], "park": [7, 13], "photograph": [7, 13], "bruce": [7, 13], "wayn": [7, 13], "ceo": [7, 13], "spiderman": [7, 13], "batman": [7, 11, 13], "flight": 7, "durabl": 7, "75": 7, "80": [7, 11], "70": 7, "allowed_pattern": 7, "rec_id": 7, "deni": 7, "record_id": [7, 15], "get_var": [7, 14], "post_var": [7, 14], "diagram": 7, "formdata": 7, "eq": 7, "stand": [7, 9, 11], "gt": [7, 9], "greater": 7, "record": [7, 11, 12, 13, 14, 15], "said": 7, "statu": 7, "200": [7, 11, 14, 15], "2019": 7, "05": 7, "19t05": 7, "38": 7, "00": 7, "132635": 7, "api_vers": 7, "2021": 7, "01": 7, "04t07": 7, "466030": 7, "regex": [7, 9, 11], "post_writ": 7, "referenced_bi": 7, "put_writ": 7, "178974": 7, "123218": 7, "collaps": [7, 9], "559918": 7, "201988": 7, "322494": 7, "309903": 7, "355181": 7, "34": 7, "974953": 7, "405515": 7, "366288": 7, "451907": 7, "453020": 7, "iso": 7, "8601": 7, "org": [7, 9, 15], "representational_state_transf": 7, "distinct": [8, 11], "renoir": 8, "newer": 8, "minor": [8, 14], "trickeri": 8, "seamlessli": 8, "embed": [8, 11], "angl": 8, "mix": 8, "soon": [8, 15], "indent": 8, "un": [8, 9, 10, 15], "keyword": [8, 9], "colon": 8, "begin": [8, 11, 12, 13], "emac": 8, "signifi": 8, "divis": [8, 9, 11], "br": [8, 9], "dom": [8, 16], "inspector": 8, "recurs": 8, "vulner": 8, "dummyrespons": 8, "new_app": [8, 9], "iter": [8, 11], "ul": [8, 11, 12, 15], "li": [8, 11, 12, 15], "row": [8, 9, 11, 13, 14, 15], "randint": [8, 12], "h2": [8, 11], "45": [8, 11], "incorrect": 8, "recal": 8, "itemize1": 8, "href": [8, 9, 12, 13], "itemize2": 8, "footer": [8, 9], "minimalist": [8, 14], "minimalist_pag": 8, "bytecod": 8, "pyc": 8, "sidebar_en": 8, "home": 8, "excerpt": 8, "sidebar": 8, "anywher": [8, 14], "worth": 8, "though": 8, "impos": 8, "signific": 8, "some_condit": 8, "this_templ": 8, "that_templ": 8, "encapsul": [8, 9], "menu": [8, 11, 12], "mysidebar": 8, "predefin": 8, "ride": 8, "doctyp": [8, 13], "meta": [8, 9], "viewport": 8, "width": [8, 11], "devic": 8, "shortcut": [8, 11], "icon": [8, 13], "aaabaaeaaqeaaaeaiaawaaaafgaaacgaaaabaaaaagaaaaeaiaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaapaaaaaa": 8, "stylesheet": [8, 13], "cdnj": [8, 13], "cloudflar": [8, 13], "ajax": [8, 13, 15], "font": [8, 11, 13], "awesom": [8, 13], "14": [8, 11, 13], "min": [8, 11, 13], "sha512": [8, 11], "1pkogiy59xj8co8": 8, "ne6fz": 8, "loazkji": 8, "ky8iq0g4b3cyey6wyhn3yt9pw0xpsrivlkmxe40ptknxrlnz9": 8, "fkdaog": 8, "crossorigin": 8, "margin": [8, 15], "top": [8, 10, 11, 12], "16px": 8, "8em": [8, 15], "page_head": 8, "navig": [8, 13, 15], "bar": [8, 13], "nav": 8, "black": [8, 11], "touch": 8, "hamburg": 8, "checkbox": [8, 9], "left": [8, 11, 13, 15], "page_left_menu": 8, "navbar": [8, 12], "primari": [8, 12], "change_password": [8, 12], "flash": [8, 15, 16], "pad": [8, 14, 15], "contect": 8, "gotta": 8, "page_script": 8, "html5": 8, "58": 8, "accordingli": 8, "54": 8, "Of": 8, "123": [9, 11], "myclass": 9, "thisisatest": 9, "metatag": 9, "tagger": 9, "xmlescap": 9, "shell": 9, "represent": 9, "ab": [9, 11], "yb": 9, "hyphen": 9, "_data": 9, "soap": 9, "whatev": [9, 15], "_xmln": 9, "xmln": 9, "permitted_tag": 9, "allowed_attribut": 9, "blockquot": 9, "cite": [9, 11], "alt": 9, "colspan": 9, "me": [9, 11, 13, 15], "_bgcolor": 9, "bgcolor": 9, "bold": 9, "emphas": 9, "_action": 9, "_method": 9, "subhead": 9, "ital": 9, "emb": 9, "_src": 9, "png": [9, 11], "_alt": 9, "radio": [9, 11], "_valu": [9, 11, 15], "_check": 9, "ok": [9, 14], "inlin": [9, 15], "block": [9, 12, 15], "white": [9, 15], "arrai": [9, 15], "_col": 9, "_row": 9, "col": 9, "typewrit": 9, "monospac": 9, "unord": 9, "_c": 9, "suppress": 9, "ref": [9, 11], "compound": 9, "nice": 9, "0x7fa533ff7640": 9, "karg": 9, "suppli": 9, "first_onli": 9, "z": [9, 11], "jqueri": [9, 11, 15], "selector": [9, 15], "selector1": 9, "selector2": 9, "selectorn": 9, "descend": 9, "ancestor": 9, "_u": 9, "_disabl": 9, "abc": [9, 11], "xyz": [9, 11], "callabl": [9, 11], "el": 9, "textual": 9, "efg": 9, "timeoffset": 9, "sidebar_menu": 9, "i18n": 10, "p10n": 10, "IT": 10, "fr": 10, "dog": [10, 11, 15], "cane": [10, 15], "cani": [10, 15], "tantissimi": 10, "bed": 10, "czech": 10, "postel": 10, "postel\u00ed": 10, "warp": 10, "max": [10, 11, 15], "find_match": 10, "discov": 10, "update_languag": 10, "german": 10, "known_express": 10, "high": [11, 12], "formstyl": [11, 13, 15], "formstyledefault": [11, 13], "dbio": 11, "keep_valu": 11, "form_nam": 11, "csrf_session": 11, "csrf_protect": 11, "lifespan": 11, "signing_info": 11, "formstylebulma": [11, 13, 15], "formstylebootstrap4": 11, "form_minim": 11, "product_nam": 11, "product_quant": 11, "not_accept": 11, "form_exampl": 11, "intention": 11, "form_bas": 11, "realnam": 11, "dc": [11, 12], "comic": 11, "marvel": 11, "dual": 11, "bottom": 11, "dropdown": [11, 13, 15], "prototyp": [11, 15], "your_app": 11, "form_upload": 11, "required_fold": 11, "checkboxwidget": 11, "datetimewidget": 11, "fileuploadwidget": 11, "listwidget": 11, "passwordwidget": 11, "radiowidget": 11, "selectwidget": 11, "textareawidget": 11, "improv": [11, 12, 13], "form_widget": 11, "form_custom_widget": 11, "mycustomwidget": 11, "no_tabl": [11, 15], "s_": [11, 15], "_placehold": [11, 15], "_titl": [11, 15], "_style": [11, 15], "mystyl": 11, "foreground": 11, "granulari": 11, "submiss": [11, 15], "detail_field": 11, "clash": 11, "isn": [11, 13], "surround": 11, "correctli": 11, "stuff": [11, 13], "param": [11, 12, 13, 14, 15], "_onclick": 11, "doh": 11, "cancel": [11, 15], "attr": [11, 13, 15], "histori": 11, "drop": 11, "error_messag": 11, "fist": 11, "fill": [11, 15], "letter": 11, "alphanumer": 11, "lower": 11, "phone": 11, "strict": 11, "substr": 11, "fit": 11, "boundari": 11, "maxsiz": 11, "255": 11, "minsiz": 11, "16": 11, "1kb": 11, "1mb": 11, "1048576": 11, "1024": 11, "cgi": 11, "fieldstorag": 11, "intuit": 11, "reject": 11, "scheme": 11, "domain": [11, 12], "rfc": 11, "2616": 11, "semant": [11, 12], "abbrevi": 11, "ca": 11, "2396": 11, "allowed_schem": 11, "exclud": 11, "prepend_schem": 11, "idn": 11, "3490": 11, "ascii": 11, "punycod": 11, "3492": 11, "bit": 11, "beyond": 11, "hex": 11, "0x4e86": 11, "becom": 11, "4e": 11, "86": 11, "ftp": 11, "maxlen": 11, "slug": 11, "dash": 11, "native_json": 11, "hh": 11, "mm": 11, "ss": 11, "yyyi": 11, "dd": 11, "symbol": 11, "year": [11, 15], "centuri": 11, "1963": 11, "dai": 11, "month": 11, "28": 11, "08": 11, "aug": 11, "august": 11, "hour": 11, "24": 11, "clock": 11, "12": 11, "am": 11, "pm": 11, "minut": 11, "59": 11, "2008": 11, "2009": 11, "empty_regex": 11, "dot": 11, "fall": 11, "arithmet": 11, "ie": 11, "convers": [11, 16], "prime": 11, "appl": 11, "banana": 11, "cherri": 11, "alphabet": 11, "hulk": 11, "06": 11, "multiselect": 11, "forbidden": 11, "clever": 11, "enough": [11, 12, 13], "entropi": 11, "53": 11, "pbkdf2": 11, "md5": 11, "hmac": 11, "thisisthekei": 11, "useless": [11, 14], "salt": 11, "constant": 11, "mysaltvalu": 11, "Or": 11, "somewhat": 11, "min_length": 11, "alg": 11, "filter_in": 11, "split_email": 11, "findal": 11, "mailto": 11, "textarea": 11, "blank": [11, 13], "onvalid": 11, "emails_onvalid": 11, "attempt": [11, 12], "through": 11, "dimens": 11, "height": [11, 13, 15], "bmp": 11, "gif": 11, "jpeg": 11, "taken": 11, "source1": 11, "lowercas": 11, "bypass": [11, 12, 13], "200x200": 11, "pixel": 11, "lastdot": 11, "tar": 11, "gz": 11, "thumbnail": 11, "jpg": 11, "older": 11, "regexlib": 11, "minip": 11, "maxip": 11, "is_localhost": 11, "is_priv": 11, "is_automat": 11, "lowest": 11, "highest": 11, "flag": 11, "168": 11, "199": 11, "forbid": 11, "169": 11, "254": 11, "network": [11, 12], "is_link_loc": 11, "is_reserv": 11, "is_multicast": 11, "is_rout": 11, "is_6to4": 11, "is_teredo": 11, "subnet": 11, "ipv6": 11, "alloc": 11, "reserv": 11, "fe80": 11, "ietf": 11, "multicast": 11, "ff00": 11, "6to4": 11, "2002": 11, "teredo": 11, "2001": 11, "member": [11, 12], "fb00": 11, "456": 11, "123456": 11, "synopsi": 11, "her": 11, "race": 11, "operationalerror": 11, "dbset": 11, "allowed_overrid": 11, "registration_stamp": 11, "timedelta": 11, "value_field": 11, "representing_field": 11, "fourth": 11, "ten": 11, "orderbi": [11, 12, 13, 15], "groupbi": 11, "useful": 11, "_and": 11, "check_nonnegative_quant": 11, "vital": 12, "multius": 12, "interchang": 12, "guidelin": 12, "approv": 12, "request_reset_password": 12, "reset_password": 12, "verify_email": 12, "change_email": 12, "allowed_act": 12, "verif": 12, "successfulli": 12, "challeng": 12, "two_factor_filt": 12, "sampl": 12, "user_outside_network": 12, "ipaddress": 12, "22": 12, "ip_list": 12, "ipv4network": 12, "ipv4address": 12, "mfa": 12, "send_two_factor_email": 12, "sender": 12, "from_address": 12, "youremail": 12, "flow": 12, "two_factor": 12, "endpoint": [12, 15], "_next_url": 12, "auth_plugin": 12, "hierarch": 12, "saml": 12, "oauth": 12, "adapt": [12, 15], "pam_plugin": 12, "pamplugin": 12, "register_plugin": 12, "mount": 12, "directori": 12, "ldap_plugin": 12, "ldapplugin": 12, "ldap_set": 12, "base_dn": 12, "cn": 12, "ubuntu": 12, "apt": 12, "libldap2": 12, "dev": 12, "libsasl2": 12, "oauth2googl": 12, "client_id": 12, "client_secret": 12, "callback_url": 12, "callback": [12, 15], "oauth2facebook": 12, "oauth2discord": 12, "discord_client_id": 12, "discord_client_secret": 12, "uri": 12, "discrimin": 12, "overkil": 12, "tagged_db": 12, "_tag": 12, "tagged_nam": 12, "dancer": 12, "teacher": 12, "auth_user_tagged_group": 12, "foot": 12, "not_author": 12, "find_by_tag": 12, "has_membership": 12, "school": 12, "physic": 12, "creativ": 12, "auth_group": 12, "zapper": 12, "zap_id": 12, "zap": 12, "belong": [12, 14], "further": 12, "corner": 13, "stone": 13, "sort": 13, "desc": 13, "search_queri": 13, "search_form": 13, "jpsteil": 13, "grid_tutori": 13, "doubt": 13, "preciou": 13, "hint": [13, 16], "gridclassstylebulma": 13, "grid_class_styl": 13, "gridclassstyl": 13, "val": [13, 15], "perfectli": 13, "usabl": 13, "refresh": 13, "lead": 13, "topic": [13, 16], "htmx": [13, 16], "field_id": 13, "show_id": 13, "pre_action_button": 13, "post_action_button": 13, "auto_process": [13, 15], "rows_per_pag": 13, "include_action_button_text": 13, "search_button_text": 13, "action_button": 13, "gather": 13, "dure": 13, "addition": 13, "fifth": 13, "clickabl": 13, "portion": 13, "primarili": 13, "class_styl": 13, "bootstrap": 13, "init": 13, "gridactionbutton": 13, "additional_class": 13, "additional_styl": 13, "override_class": 13, "override_styl": 13, "append_id": 13, "ignore_attribute_plugin": 13, "fa": 13, "calendar": 13, "id_field_nam": 13, "id_valu": 13, "querystr": 13, "10px": 13, "foo": 13, "coupl": [13, 15], "filter_out": 13, "compani": 13, "is_null_or": 13, "downfal": 13, "search_text": 13, "migrat": [14, 16], "predetermin": 14, "constrain": 14, "my_url_path": 14, "postfix": 14, "matter": 14, "wrapper": [14, 15], "hard": 14, "analogi": 14, "hous": 14, "preprocess": 14, "postprocess": 14, "carri": 14, "underli": 14, "exact": 14, "danger": 14, "pyweb": 14, "abil": 14, "requires_": 14, "rocket": 14, "301": 14, "sophist": 14, "file_path": 14, "csv": 14, "app_fold": 14, "requires_login": 14, "user_email": 14, "fetch": [14, 15], "websocket": 15, "async": 15, "great": 15, "react": 15, "reap": 15, "ecosystem": 15, "road": 15, "reactiv": 15, "emerg": 15, "leader": 15, "technic": 15, "transit": 15, "socket": 15, "hypertext": 15, "cit1601": 15, "frontend": 15, "hx": 15, "_hx": 15, "url_to_post_to": 15, "htmx_form_demo": 15, "htmx_list": 15, "htmx_form": 15, "cancel_attr": 15, "sidecar": 15, "unpkg": 15, "sh": 15, "mainten": 15, "anchor": 15, "fanci": 15, "htmx_grid": 15, "attributes_plugin": 15, "attributespluginhtmx": 15, "new_sidecar": 15, "edit_sidecar": 15, "functool": 15, "autocomplete_queri": 15, "fk_tabl": 15, "ktabl": 15, "fk_field": 15, "kfield": 15, "_autocomplete_search_field": 15, "sf": 15, "_search": 15, "len": 15, "data_label": 15, "htmxautocompletewidget": 15, "simple_queri": 15, "pop": 15, "todo": 15, "hidden_input": 15, "hidden_div": 15, "keyup": 15, "500m": 15, "s_autocomplete_result": 15, "search_valu": 15, "s_search": 15, "_autocomplet": 15, "onload": 15, "elt": 15, "queryselector": 15, "onkeydown": 15, "check_": 15, "s_down_kei": 15, "keycod": 15, "s_autocomplet": 15, "focu": 15, "selectedindex": 15, "formatt": 15, "formstylefactori": 15, "class_inner_except": 15, "vendor": 15, "vendor_typ": 15, "product_record": 15, "earlier": 15, "whichev": 15, "subqueri": 15, "builder": 15, "selected_el": 15, "queryselectoral": 15, "eval": 15, "sandbox": 15, "math": 15, "nicer": 15, "recereiv": 15, "get_cooki": 15, "register_vue_compon": 15, "lazili": 15, "upload_help": 15, "bind": 15, "my_id": 15, "reimplement": 15, "tanti": 15, "clientsid": 15, "serversid": 15, "debounc": 15, "setinterv": 15, "500": 15, "200m": 15, "onclick": 15, "1000m": 15, "tags_input": 15, "jsl": 15, "zip_cod": 15, "freetext": 15, "safari": 15, "edg": 15, "datalist": 15, "firfox": 15, "undocu": 15, "border": 15, "radiu": 15, "100px": 15, "111111": 15, "3em": 15, "2em": 15, "2px": 15, "pointer": 15, "opac": 15, "capit": 15, "tag_input": 15, "score_input": 15, "score": 15, "new_password": 15, "poor": 15, "man": 15, "trap": 15, "component_1": 15, "mycompon": 15, "blink": 15, "envelop": 15, "other_pag": 15, "acknowledg": 16, "procedur": 16, "scratch": 16, "urlsign": 16, "memoiz": 16, "raw": 16, "sql": 16, "export": 16, "gotcha": 16, "overview": 16, "asyncio": 16}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"what": 0, "i": [0, 9], "py4web": [0, 1, 6, 14, 15, 16], "acknowledg": 0, "help": 1, "resourc": 1, "hint": 1, "thi": 1, "manual": [1, 16], "The": [1, 3, 4, 5, 6, 7, 11, 13, 15], "googl": [1, 2, 6, 12], "group": [1, 6], "discord": [1, 12], "server": [1, 5, 6, 9], "tutori": 1, "video": 1, "sourc": [1, 2], "github": 1, "tip": 1, "prerequisit": [1, 2], "A": [1, 9, 11], "modern": 1, "python": 1, "workplac": 1, "debug": 1, "vscode": 1, "pycharm": 1, "how": 1, "contribut": 1, "instal": 2, "startup": 2, "understand": 2, "design": [2, 11], "support": [2, 6], "platform": 2, "setup": 2, "procedur": 2, "from": [2, 4, 6, 14], "binari": 2, "pip": 2, "us": [2, 5, 6, 8, 9, 12, 13, 15], "virtual": [2, 6], "environ": 2, "global": 2, "local": 2, "upgrad": 2, "first": [2, 4, 6], "run": [2, 6], "command": [2, 6], "line": 2, "option": [2, 9, 11], "call": [2, 14], "new_app": 2, "set_password": 2, "shell": [2, 6], "version": [2, 6], "special": [2, 11], "http": 2, "wsgi": 2, "deploy": 2, "gcloud": 2, "aka": 2, "gae": 2, "app": [2, 4], "engin": 2, "pythonanywher": 2, "com": 2, "docker": 2, "podman": 2, "ubuntu": 2, "dashboard": 3, "main": 3, "web": [3, 4], "page": [3, 4, 8], "login": 3, "creat": 4, "your": 4, "scratch": 4, "static": 4, "dynam": 4, "On": 4, "return": [4, 8, 14], "valu": [4, 6], "rout": 4, "request": 4, "object": [4, 12, 13, 15], "templat": [4, 5, 8, 13], "_scaffold": 4, "copi": [4, 6], "watch": 4, "file": [4, 10, 11, 14], "chang": 4, "fixtur": 5, "inject": [5, 9], "translat": [5, 10], "flash": [5, 14], "session": 5, "client": 5, "side": [5, 9], "cooki": 5, "memcach": 5, "redi": 5, "databas": [5, 6, 11], "anywher": 5, "share": 5, "condit": 5, "urlsign": 5, "dal": [5, 6], "auth": [5, 12, 14], "caveat": 5, "about": [5, 6], "custom": [5, 6, 9, 11, 13], "multipl": [5, 12], "cach": [5, 6], "memoiz": 5, "conveni": 5, "decor": 5, "abstract": 6, "layer": 6, "introduct": 6, "model": 6, "quick": 6, "tour": 6, "stand": 6, "alon": 6, "experi": 6, "constructor": [6, 11], "signatur": 6, "connect": 6, "string": [6, 15], "uri": 6, "paramet": [6, 11, 13], "pool": 6, "failur": 6, "attempt": 6, "lazi": 6, "tabl": [6, 9, 16], "less": 6, "applic": 6, "replic": 6, "reserv": 6, "keyword": 6, "quot": 6, "case": 6, "set": [6, 11, 13, 14], "make": 6, "secur": [6, 11], "other": [6, 11], "folder": 6, "locat": 6, "default": [6, 8], "migrat": 6, "commit": 6, "rollback": 6, "define_t": 6, "id": 6, "note": 6, "primari": 6, "kei": [6, 13], "plural": [6, 10], "singular": 6, "redefin": 6, "format": [6, 11, 15], "record": 6, "represent": 6, "rname": 6, "real": 6, "name": 6, "primarykei": 6, "legaci": 6, "fake_migr": 6, "table_class": 6, "sequence_nam": 6, "trigger_nam": 6, "polymodel": 6, "on_defin": 6, "ad": 6, "attribut": 6, "field": [6, 11, 13], "type": [6, 11], "valid": [6, 11], "time": [6, 11], "modif": 6, "more": 6, "upload": [6, 11], "fix": 6, "broken": 6, "control": 6, "summari": 6, "method": [6, 14], "insert": 6, "queri": 6, "row": 6, "update_or_insert": 6, "validate_and_insert": 6, "validate_and_upd": 6, "drop": 6, "tag": [6, 9, 11, 12], "raw": 6, "sql": 6, "executesql": 6, "_lastsql": 6, "index": 6, "gener": 6, "select": [6, 9], "an": 6, "iter": 6, "base": 6, "lower": 6, "memori": 6, "render": 6, "repres": 6, "shortcut": 6, "fetch": 6, "recurs": 6, "orderbi": 6, "groupbi": 6, "limitbi": 6, "distinct": 6, "have": 6, "orderby_on_limitbi": 6, "join": 6, "left": 6, "cacheabl": 6, "logic": 6, "oper": 6, "count": 6, "isempti": 6, "delet": 6, "updat": [6, 10], "express": 6, "update_record": 6, "dictionari": [6, 11], "last": 6, "as_dict": 6, "as_list": 6, "combin": 6, "find": [6, 9], "exclud": 6, "sort": [6, 11], "comput": 6, "new": 6, "style": [6, 9, 13], "experiment": 6, "old": 6, "relat": 6, "One": 6, "mani": 6, "inner": 6, "outer": 6, "self": 6, "refer": [6, 13, 16], "alias": 6, "like": 6, "ilik": 6, "regexp": 6, "startswith": 6, "endswith": 6, "contain": 6, "upper": 6, "year": 6, "month": 6, "dai": 6, "hour": 6, "minut": 6, "second": 6, "belong": 6, "sum": 6, "avg": 6, "min": 6, "max": 6, "len": 6, "substr": 6, "coalesc": 6, "coalesce_zero": 6, "export": 6, "import": 6, "data": 6, "csv": 6, "one": 6, "all": 6, "onc": 6, "remot": 6, "synchron": 6, "html": [6, 9], "xml": [6, 9], "advanc": [6, 11, 15], "featur": [6, 13], "list": 6, "inherit": 6, "filter_in": 6, "filter_out": 6, "callback": 6, "cascad": 6, "common": 6, "filter": [6, 13], "without": [6, 11], "defin": 6, "distribut": 6, "transact": 6, "db": 6, "anoth": 6, "gotcha": 6, "adapt": 6, "sqlite": 6, "mysql": 6, "mssql": 6, "microsoft": 6, "oracl": 6, "nosql": 6, "datastor": 6, "restapi": 7, "polici": 7, "action": [7, 12, 13], "get": 7, "practic": 7, "exampl": [7, 11, 13, 14, 15], "respons": 7, "yatl": [8, 9], "languag": 8, "basic": [8, 11, 13], "syntax": 8, "while": 8, "elif": 8, "els": 8, "try": 8, "except": 8, "final": 8, "def": 8, "inform": 8, "workflow": 8, "extend": 8, "includ": 8, "variabl": [8, 14], "function": [8, 11], "block": 8, "super": 8, "layout": 8, "standard": [8, 11], "structur": [8, 11], "mobil": 8, "develop": 8, "helper": 9, "overview": 9, "built": 9, "bodi": 9, "cat": 9, "div": 9, "em": 9, "form": [9, 11, 14, 15], "h1": 9, "h2": 9, "h3": 9, "h4": 9, "h5": 9, "h6": 9, "head": 9, "img": 9, "input": 9, "label": 9, "li": 9, "ol": 9, "p": 9, "pre": 9, "script": 9, "span": 9, "tr": 9, "td": 9, "tbodi": 9, "textarea": 9, "th": 9, "thead": 9, "titl": 9, "tt": 9, "ul": 9, "url": 9, "beautifi": 9, "dom": 9, "children": 9, "internation": 10, "minim": 11, "widget": [11, 15], "manipul": 11, "sidecar": 11, "text": 11, "is_alphanumer": 11, "is_low": 11, "is_upp": 11, "is_email": 11, "is_match": 11, "is_length": 11, "is_url": 11, "is_slug": 11, "is_json": 11, "date": 11, "is_tim": 11, "is_dat": 11, "is_datetim": 11, "is_date_in_rang": 11, "is_datetime_in_rang": 11, "rang": 11, "equal": 11, "is_equal_to": 11, "is_not_empti": 11, "is_null_or": 11, "is_empty_or": 11, "is_expr": 11, "is_decimal_in_rang": 11, "is_float_in_rang": 11, "is_int_in_rang": 11, "is_in_set": 11, "checkbox": 11, "tupl": 11, "complex": 11, "is_strong": 11, "crypt": 11, "is_list_of": 11, "is_list_of_email": 11, "any_of": 11, "is_imag": 11, "is_fil": 11, "is_upload_filenam": 11, "is_ipv4": 11, "is_ipv6": 11, "is_ipaddress": 11, "cleanup": 11, "is_not_in_db": 11, "is_in_db": 11, "authent": 12, "author": 12, "ui": 12, "insid": 12, "two": 12, "factor": 12, "two_factor_requir": 12, "two_factor_send": 12, "two_factor_tri": 12, "plugin": 12, "pam": 12, "ldap": 12, "oauth2": 12, "facebook": 12, "permiss": 12, "grid": [13, 14, 15], "search": 13, "crud": 13, "column": 13, "button": 13, "sampl": 13, "class": 13, "callabl": 13, "web2pi": 14, "simpl": 14, "convers": 14, "hello": 14, "world": 14, "redirect": 14, "arg": 14, "up": 14, "counter": 14, "view": 14, "access": 14, "o": 14, "topic": 15, "asyncio": 15, "htmx": 15, "usag": 15, "autocomplet": 15, "util": 15, "j": 15, "q": 15, "t": 15, "content": 16, "indic": 16}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"What is py4web?": [[0, "what-is-py4web"]], "Acknowledgments": [[0, "acknowledgments"]], "Help, resources and hints": [[1, "help-resources-and-hints"]], "Resources": [[1, "resources"]], "This manual": [[1, "this-manual"]], "The Google group": [[1, "the-google-group"]], "The Discord server": [[1, "the-discord-server"]], "Tutorials and video": [[1, "tutorials-and-video"]], "The sources on GitHub": [[1, "the-sources-on-github"]], "Hints and tips": [[1, "hints-and-tips"]], "Prerequisites": [[1, "prerequisites"]], "A modern python workplace": [[1, "a-modern-python-workplace"]], "Debugging py4web with VScode": [[1, "debugging-py4web-with-vscode"]], "Debugging py4web with PyCharm": [[1, "debugging-py4web-with-pycharm"]], "How to contribute": [[1, "how-to-contribute"]], "Installation and Startup": [[2, "installation-and-startup"]], "Understanding the design": [[2, "understanding-the-design"]], "Supported platforms and prerequisites": [[2, "supported-platforms-and-prerequisites"]], "Setup procedures": [[2, "setup-procedures"]], "Installing from binaries": [[2, "installing-from-binaries"]], "Installing from pip": [[2, "installing-from-pip"]], "Installing using a virtual environment": [[2, "installing-using-a-virtual-environment"]], "Installing from source (globally)": [[2, "installing-from-source-globally"]], "Installing from source (locally)": [[2, "installing-from-source-locally"]], "Upgrading": [[2, "upgrading"]], "First run": [[2, "first-run"]], "Command line options": [[2, "command-line-options"]], "call command option": [[2, "call-command-option"]], "new_app command option": [[2, "new-app-command-option"]], "run command option": [[2, "run-command-option"]], "set_password command option": [[2, "set-password-command-option"]], "setup command option": [[2, "setup-command-option"]], "shell command option": [[2, "shell-command-option"]], "version command option": [[2, "version-command-option"]], "Special installations": [[2, "special-installations"]], "HTTPS": [[2, "https"]], "WSGI": [[2, "wsgi"]], "Deployment on GCloud (aka GAE - Google App Engine)": [[2, "deployment-on-gcloud-aka-gae-google-app-engine"]], "Deployment on PythonAnywhere.com": [[2, "deployment-on-pythonanywhere-com"]], "Deployment on Docker/Podman": [[2, "deployment-on-docker-podman"]], "Deployment on Ubuntu": [[2, "deployment-on-ubuntu"]], "The Dashboard": [[3, "the-dashboard"]], "The main Web page": [[3, "the-main-web-page"]], "Login into the Dashboard": [[3, "login-into-the-dashboard"]], "Creating your first app": [[4, "creating-your-first-app"]], "From scratch": [[4, "from-scratch"]], "Static web pages": [[4, "static-web-pages"]], "Dynamic Web Pages": [[4, "dynamic-web-pages"]], "On return values": [[4, "on-return-values"]], "Routes": [[4, "routes"]], "The request object": [[4, "the-request-object"]], "Templates": [[4, "templates"]], "The _scaffold app": [[4, "the-scaffold-app"]], "Copying the _scaffold app": [[4, "copying-the-scaffold-app"]], "Watch for files change": [[4, "watch-for-files-change"]], "Fixtures": [[5, "fixtures"]], "Using Fixtures": [[5, "using-fixtures"]], "The Template fixture": [[5, "the-template-fixture"]], "The Inject fixture": [[5, "the-inject-fixture"]], "The Translator fixture": [[5, "the-translator-fixture"]], "The Flash fixture": [[5, "the-flash-fixture"]], "The Session fixture": [[5, "the-session-fixture"]], "Client-side session in cookies": [[5, "client-side-session-in-cookies"]], "Server-side session in memcache": [[5, "server-side-session-in-memcache"]], "Server-side session in Redis": [[5, "server-side-session-in-redis"]], "Server-side session in database": [[5, "server-side-session-in-database"]], "Server-side session anywhere": [[5, "server-side-session-anywhere"]], "Sharing sessions": [[5, "sharing-sessions"]], "The Condition fixture": [[5, "the-condition-fixture"]], "The URLsigner fixture": [[5, "the-urlsigner-fixture"]], "The DAL fixture": [[5, "the-dal-fixture"]], "The Auth fixture": [[5, "the-auth-fixture"]], "Caveats about fixtures": [[5, "caveats-about-fixtures"]], "Custom fixtures": [[5, "custom-fixtures"]], "Multiple fixtures": [[5, "multiple-fixtures"]], "Caching and Memoize": [[5, "caching-and-memoize"]], "Convenience Decorators": [[5, "convenience-decorators"]], "The Database Abstraction Layer (DAL)": [[6, "the-database-abstraction-layer-dal"]], "DAL introduction": [[6, "dal-introduction"]], "py4web model": [[6, "py4web-model"]], "Supported databases": [[6, "supported-databases"]], "The DAL: a quick tour": [[6, "the-dal-a-quick-tour"]], "Using the DAL \u201cstand-alone\u201d": [[6, "using-the-dal-stand-alone"]], "Experiment with the py4web shell": [[6, "experiment-with-the-py4web-shell"]], "DAL constructor": [[6, "dal-constructor"]], "DAL signature": [[6, "dal-signature"]], "Connection strings (the uri parameter)": [[6, "connection-strings-the-uri-parameter"]], "Connection pooling": [[6, "connection-pooling"]], "Connection failures (attempts parameter)": [[6, "connection-failures-attempts-parameter"]], "Lazy Tables": [[6, "lazy-tables"]], "Model-less applications": [[6, "model-less-applications"]], "Replicated databases": [[6, "replicated-databases"]], "Reserved keywords": [[6, "reserved-keywords"]], "Database quoting and case settings": [[6, "database-quoting-and-case-settings"]], "Making a secure connection": [[6, "making-a-secure-connection"]], "Other DAL constructor parameters": [[6, "other-dal-constructor-parameters"]], "Database folder location": [[6, "database-folder-location"]], "Default migration settings": [[6, "default-migration-settings"]], "commit and rollback": [[6, "commit-and-rollback"]], "Table constructor": [[6, "table-constructor"]], "define_table signature": [[6, "define-table-signature"]], "id: Notes about the primary key": [[6, "id-notes-about-the-primary-key"]], "plural and singular": [[6, "plural-and-singular"]], "redefine": [[6, "redefine"]], "format: Record representation": [[6, "format-record-representation"]], "rname: Real name": [[6, "rname-real-name"]], "primarykey: Support for legacy tables": [[6, "primarykey-support-for-legacy-tables"]], "migrate, fake_migrate": [[6, "migrate-fake-migrate"]], "table_class": [[6, "table-class"]], "sequence_name": [[6, "sequence-name"]], "trigger_name": [[6, "trigger-name"]], "polymodel": [[6, "polymodel"]], "on_define": [[6, "on-define"]], "Adding attributes to fields and tables": [[6, "adding-attributes-to-fields-and-tables"]], "Legacy databases and keyed tables": [[6, "legacy-databases-and-keyed-tables"]], "Field constructor": [[6, "field-constructor"]], "Field types and validators": [[6, "field-types-and-validators"]], "Run-time field and table modification": [[6, "run-time-field-and-table-modification"]], "More on uploads": [[6, "more-on-uploads"]], "Migrations": [[6, "migrations"]], "Fixing broken migrations": [[6, "fixing-broken-migrations"]], "Migration control summary": [[6, "migration-control-summary"]], "Table methods": [[6, "table-methods"]], "insert": [[6, "insert"]], "Query, Set, Rows": [[6, "query-set-rows"]], "update_or_insert": [[6, "update-or-insert"]], "validate_and_insert, validate_and_update": [[6, "validate-and-insert-validate-and-update"]], "drop": [[6, "drop"]], "Tagging records": [[6, "tagging-records"]], "Raw SQL": [[6, "raw-sql"]], "executesql": [[6, "executesql"]], "_lastsql": [[6, "lastsql"]], "Timing queries": [[6, "timing-queries"]], "Indexes": [[6, "indexes"]], "Generating raw SQL": [[6, "generating-raw-sql"]], "select command": [[6, "select-command"]], "Using an iterator-based select for lower memory use": [[6, "using-an-iterator-based-select-for-lower-memory-use"]], "Rendering rows using represent": [[6, "rendering-rows-using-represent"]], "Shortcuts": [[6, "shortcuts"]], "Fetching a Row": [[6, "fetching-a-row"]], "Recursive selects": [[6, "recursive-selects"]], "orderby, groupby, limitby, distinct, having, orderby_on_limitby, join, left, cache": [[6, "orderby-groupby-limitby-distinct-having-orderby-on-limitby-join-left-cache"]], "orderby": [[6, "orderby"]], "groupby, having": [[6, "groupby-having"]], "distinct": [[6, "distinct"]], "limitby": [[6, "limitby"]], "orderby_on_limitby": [[6, "orderby-on-limitby"]], "join, left": [[6, "join-left"]], "cache, cacheable": [[6, "cache-cacheable"]], "Logical operators": [[6, "logical-operators"]], "count, isempty, delete, update": [[6, "count-isempty-delete-update"]], "Expressions": [[6, "expressions"]], "case": [[6, "case"]], "update_record": [[6, "update-record"]], "Inserting and updating from a dictionary": [[6, "inserting-and-updating-from-a-dictionary"]], "first and last": [[6, "first-and-last"]], "as_dict and as_list": [[6, "as-dict-and-as-list"]], "Combining rows": [[6, "combining-rows"]], "find, exclude, sort": [[6, "find-exclude-sort"]], "Caching selects": [[6, "caching-selects"]], "Computed and Virtual fields": [[6, "computed-and-virtual-fields"]], "Computed fields": [[6, "computed-fields"]], "Virtual fields": [[6, "virtual-fields"]], "New style virtual fields (experimental)": [[6, "new-style-virtual-fields-experimental"]], "Old style virtual fields": [[6, "old-style-virtual-fields"]], "Joins and Relations": [[6, "joins-and-relations"]], "One to many relation": [[6, "one-to-many-relation"]], "Inner join": [[6, "inner-join"]], "Left outer join": [[6, "left-outer-join"]], "Grouping and counting": [[6, "grouping-and-counting"]], "Many to many relation": [[6, "many-to-many-relation"]], "Self-Reference and aliases": [[6, "self-reference-and-aliases"]], "Other operators": [[6, "other-operators"]], "like, ilike, regexp, startswith, endswith, contains, upper, lower": [[6, "like-ilike-regexp-startswith-endswith-contains-upper-lower"]], "year, month, day, hour, minutes, seconds": [[6, "year-month-day-hour-minutes-seconds"]], "belongs": [[6, "belongs"]], "sum, avg, min, max and len": [[6, "sum-avg-min-max-and-len"]], "Substrings": [[6, "substrings"]], "Default values with coalesce and coalesce_zero": [[6, "default-values-with-coalesce-and-coalesce-zero"]], "Exporting and importing data": [[6, "exporting-and-importing-data"]], "CSV (one Table at a time)": [[6, "csv-one-table-at-a-time"]], "CSV (all tables at once)": [[6, "csv-all-tables-at-once"]], "CSV and remote database synchronization": [[6, "csv-and-remote-database-synchronization"]], "HTML and XML (one Table at a time)": [[6, "html-and-xml-one-table-at-a-time"]], "Data representation": [[6, "data-representation"]], "Advanced features": [[6, "advanced-features"]], "list: and contains": [[6, "list-type-and-contains"]], "Table inheritance": [[6, "table-inheritance"]], "filter_in and filter_out": [[6, "filter-in-and-filter-out"]], "callbacks on record insert, delete and update": [[6, "callbacks-on-record-insert-delete-and-update"]], "Database cascades": [[6, "database-cascades"]], "Record versioning": [[6, "record-versioning"]], "Common filters": [[6, "common-filters"]], "Custom Field types": [[6, "custom-field-types"]], "Using DAL without define tables": [[6, "using-dal-without-define-tables"]], "Distributed transaction": [[6, "distributed-transaction"]], "Copy data from one db into another": [[6, "copy-data-from-one-db-into-another"]], "Gotchas": [[6, "gotchas"]], "Note on new DAL and adapters": [[6, "note-on-new-dal-and-adapters"]], "SQLite": [[6, "sqlite"]], "MySQL": [[6, "mysql"]], "Google SQL": [[6, "google-sql"]], "MSSQL (Microsoft SQL Server)": [[6, "mssql-microsoft-sql-server"]], "Oracle": [[6, "oracle"]], "Google NoSQL (Datastore)": [[6, "google-nosql-datastore"]], "The RestAPI": [[7, "the-restapi"]], "RestAPI policies and actions": [[7, "restapi-policies-and-actions"]], "RestAPI GET": [[7, "restapi-get"]], "RestAPI practical examples": [[7, "restapi-practical-examples"]], "The RestAPI response": [[7, "the-restapi-response"]], "YATL Template Language": [[8, "yatl-template-language"]], "Basic syntax": [[8, "basic-syntax"]], "for...in": [[8, "for-in"]], "while": [[8, "while"]], "if...elif...else": [[8, "if-elif-else"]], "try...except...else...finally": [[8, "try-except-else-finally"]], "def...return": [[8, "def-return"]], "Information workflow": [[8, "information-workflow"]], "extend and include": [[8, "extend-and-include"]], "Extending using variables": [[8, "extending-using-variables"]], "Template Functions": [[8, "template-functions"]], "block and super": [[8, "block-and-super"]], "Page layout standard structure": [[8, "page-layout-standard-structure"]], "Default page layout": [[8, "default-page-layout"]], "Mobile development": [[8, "mobile-development"]], "YATL helpers": [[9, "yatl-helpers"]], "Helpers overview": [[9, "helpers-overview"]], "Built-in helpers": [[9, "built-in-helpers"]], "XML": [[9, "xml"]], "A": [[9, "a"]], "BODY": [[9, "body"]], "CAT": [[9, "cat"]], "DIV": [[9, "div"]], "EM": [[9, "em"]], "FORM": [[9, "form"]], "H1, H2, H3, H4, H5, H6": [[9, "h1-h2-h3-h4-h5-h6"]], "HEAD": [[9, "head"]], "HTML": [[9, "html"]], "I": [[9, "i"]], "IMG": [[9, "img"]], "INPUT": [[9, "input"]], "LABEL": [[9, "label"]], "LI": [[9, "li"]], "OL": [[9, "ol"]], "OPTION": [[9, "option"]], "P": [[9, "p"]], "PRE": [[9, "pre"]], "SCRIPT": [[9, "script"]], "SELECT": [[9, "select"]], "SPAN": [[9, "span"]], "STYLE": [[9, "style"]], "TABLE, TR, TD": [[9, "table-tr-td"]], "TBODY": [[9, "tbody"]], "TEXTAREA": [[9, "textarea"]], "TH": [[9, "th"]], "THEAD": [[9, "thead"]], "TITLE": [[9, "title"]], "TT": [[9, "tt"]], "UL": [[9, "ul"]], "URL": [[9, "url"]], "Custom helpers": [[9, "custom-helpers"]], "TAG": [[9, "tag"]], "BEAUTIFY": [[9, "beautify"]], "Server-side DOM": [[9, "server-side-dom"]], "children": [[9, "children"]], "find": [[9, "find"]], "Using Inject": [[9, "using-inject"]], "Internationalization": [[10, "internationalization"]], "Pluralize": [[10, "pluralize"]], "Update the translation files": [[10, "update-the-translation-files"]], "Forms": [[11, "forms"]], "The Form constructor": [[11, "the-form-constructor"]], "A minimal form example without a database": [[11, "a-minimal-form-example-without-a-database"]], "Basic form example": [[11, "basic-form-example"]], "File upload field": [[11, "file-upload-field"]], "Widgets": [[11, "widgets"]], "Standard widgets": [[11, "standard-widgets"]], "Custom widgets": [[11, "custom-widgets"]], "Advanced form design": [[11, "advanced-form-design"]], "Form structure manipulation": [[11, "form-structure-manipulation"]], "Custom forms": [[11, "custom-forms"]], "The sidecar parameter": [[11, "the-sidecar-parameter"]], "Form validation": [[11, "form-validation"]], "Text format validators": [[11, "text-format-validators"]], "IS_ALPHANUMERIC": [[11, "is-alphanumeric"]], "IS_LOWER": [[11, "is-lower"]], "IS_UPPER": [[11, "is-upper"]], "IS_EMAIL": [[11, "is-email"]], "IS_MATCH": [[11, "is-match"]], "IS_LENGTH": [[11, "is-length"]], "IS_URL": [[11, "is-url"]], "IS_SLUG": [[11, "is-slug"]], "IS_JSON": [[11, "is-json"]], "Date and time validators": [[11, "date-and-time-validators"]], "IS_TIME": [[11, "is-time"]], "IS_DATE": [[11, "is-date"]], "IS_DATETIME": [[11, "is-datetime"]], "IS_DATE_IN_RANGE": [[11, "is-date-in-range"]], "IS_DATETIME_IN_RANGE": [[11, "is-datetime-in-range"]], "Range, set and equality validators": [[11, "range-set-and-equality-validators"]], "IS_EQUAL_TO": [[11, "is-equal-to"]], "IS_NOT_EMPTY": [[11, "is-not-empty"]], "IS_NULL_OR": [[11, "is-null-or"]], "IS_EMPTY_OR": [[11, "is-empty-or"]], "IS_EXPR": [[11, "is-expr"]], "IS_DECIMAL_IN_RANGE": [[11, "is-decimal-in-range"]], "IS_FLOAT_IN_RANGE": [[11, "is-float-in-range"]], "IS_INT_IN_RANGE": [[11, "is-int-in-range"]], "IS_IN_SET": [[11, "is-in-set"]], "Checkbox validation": [[11, "checkbox-validation"]], "Dictionaries and tuples with IS_IN_SET": [[11, "dictionaries-and-tuples-with-is-in-set"]], "Sorted options": [[11, "sorted-options"]], "IS_IN_SET and Tagging": [[11, "is-in-set-and-tagging"]], "Complexity and security validators": [[11, "complexity-and-security-validators"]], "IS_STRONG": [[11, "is-strong"]], "CRYPT": [[11, "crypt"]], "Special type validators": [[11, "special-type-validators"]], "IS_LIST_OF": [[11, "is-list-of"]], "IS_LIST_OF_EMAILS": [[11, "is-list-of-emails"]], "ANY_OF": [[11, "any-of"]], "IS_IMAGE": [[11, "is-image"]], "IS_FILE": [[11, "is-file"]], "IS_UPLOAD_FILENAME": [[11, "is-upload-filename"]], "IS_IPV4": [[11, "is-ipv4"]], "IS_IPV6": [[11, "is-ipv6"]], "IS_IPADDRESS": [[11, "is-ipaddress"]], "Other validators": [[11, "other-validators"]], "CLEANUP": [[11, "cleanup"]], "Database validators": [[11, "database-validators"]], "IS_NOT_IN_DB": [[11, "is-not-in-db"]], "IS_IN_DB": [[11, "is-in-db"]], "IS_IN_DB and Tagging": [[11, "is-in-db-and-tagging"]], "Validation functions": [[11, "validation-functions"]], "Authentication and authorization": [[12, "authentication-and-authorization"]], "Authentication using Auth": [[12, "authentication-using-auth"]], "Auth UI": [[12, "auth-ui"]], "Using Auth inside actions": [[12, "using-auth-inside-actions"]], "Two Factor Authentication": [[12, "two-factor-authentication"]], "two_factor_required": [[12, "two-factor-required"]], "two_factor_send": [[12, "two-factor-send"]], "two_factor_tries": [[12, "two-factor-tries"]], "Auth Plugins": [[12, "auth-plugins"]], "PAM": [[12, "pam"]], "LDAP": [[12, "ldap"]], "OAuth2 with Google": [[12, "oauth2-with-google"]], "OAuth2 with Facebook": [[12, "oauth2-with-facebook"]], "OAuth2 with Discord": [[12, "oauth2-with-discord"]], "Authorization using Tags": [[12, "authorization-using-tags"]], "Tags and Permissions": [[12, "tags-and-permissions"]], "Multiple Tags objects": [[12, "multiple-tags-objects"]], "Grid": [[13, "grid"]], "Key features": [[13, "key-features"]], "Basic grid example": [[13, "basic-grid-example"]], "The Grid object": [[13, "the-grid-object"]], "Searching and filtering": [[13, "searching-and-filtering"]], "CRUD settings": [[13, "crud-settings"]], "Custom columns": [[13, "custom-columns"]], "Using templates": [[13, "using-templates"]], "Customizing style": [[13, "customizing-style"]], "Custom Action Buttons": [[13, "custom-action-buttons"]], "Sample Action Button Class": [[13, "sample-action-button-class"]], "Using callable parameters": [[13, "using-callable-parameters"]], "Reference Fields": [[13, "reference-fields"]], "From web2py to py4web": [[14, "from-web2py-to-py4web"]], "Simple conversion examples": [[14, "simple-conversion-examples"]], "\u201cHello world\u201d example": [[14, "hello-world-example"]], "\u201cRedirect with variables\u201d example": [[14, "redirect-with-variables-example"]], "\u201cReturning variables\u201d example": [[14, "returning-variables-example"]], "\u201cReturning args\u201d example": [[14, "returning-args-example"]], "\u201cReturn calling methods\u201d example": [[14, "return-calling-methods-example"]], "\u201cSetting up a counter\u201d example": [[14, "setting-up-a-counter-example"]], "\u201cView\u201d example": [[14, "view-example"]], "\u201cForm and flash\u201d example": [[14, "form-and-flash-example"]], "\u201cgrid\u201d example": [[14, "grid-example"]], "\u201cAccessing OS files\u201d example": [[14, "accessing-os-files-example"]], "\u201cauth\u201d example": [[14, "auth-example"]], "Advanced topics and examples": [[15, "advanced-topics-and-examples"]], "py4web and asyncio": [[15, "py4web-and-asyncio"]], "htmx": [[15, "htmx"]], "htmx usage in Form": [[15, "htmx-usage-in-form"]], "htmx usage in Grid": [[15, "htmx-usage-in-grid"]], "Autocomplete Widget using htmx": [[15, "autocomplete-widget-using-htmx"]], "utils.js": [[15, "utils-js"]], "string.format": [[15, "string-format"]], "The Q object": [[15, "the-q-object"]], "The T object": [[15, "the-t-object"]], "py4web: the reference Manual": [[16, "py4web-the-reference-manual"]], "Contents:": [[16, null]], "Indices and tables": [[16, "indices-and-tables"]]}, "indexentries": {}}) \ No newline at end of file +Search.setIndex({"alltitles": {"A": [[10, "a"]], "A minimal form example without a database": [[12, "a-minimal-form-example-without-a-database"]], "A modern python workplace": [[1, "a-modern-python-workplace"]], "ANY_OF": [[12, "any-of"]], "Acknowledgments": [[0, "acknowledgments"]], "Adding attributes to fields and tables": [[6, "adding-attributes-to-fields-and-tables"]], "Advanced features": [[6, "advanced-features"]], "Advanced form design": [[12, "advanced-form-design"]], "Advanced topics and examples": [[16, null]], "Auth Plugins": [[13, "auth-plugins"]], "Auth UI": [[13, "auth-ui"]], "Authentication and authorization": [[13, null]], "Authentication using Auth": [[13, "authentication-using-auth"]], "Authorization using Tags": [[13, "authorization-using-tags"]], "Autocomplete Widget using htmx": [[16, "autocomplete-widget-using-htmx"]], "BEAUTIFY": [[10, "beautify"]], "BODY": [[10, "body"]], "Basic form example": [[12, "basic-form-example"]], "Basic grid example": [[14, "basic-grid-example"]], "Basic syntax": [[8, "basic-syntax"]], "Built-in helpers": [[10, "built-in-helpers"]], "CAT": [[10, "cat"]], "CLEANUP": [[12, "cleanup"]], "CRUD settings": [[14, "crud-settings"]], "CRYPT": [[12, "crypt"]], "CSV (all tables at once)": [[6, "csv-all-tables-at-once"]], "CSV (one Table at a time)": [[6, "csv-one-table-at-a-time"]], "CSV and remote database synchronization": [[6, "csv-and-remote-database-synchronization"]], "Caching and Memoize": [[5, "caching-and-memoize"]], "Caching selects": [[6, "caching-selects"]], "Caveats about fixtures": [[5, "caveats-about-fixtures"]], "Celery": [[16, "celery"]], "Checkbox validation": [[12, "checkbox-validation"]], "Client-side session in cookies": [[5, "client-side-session-in-cookies"]], "Combining rows": [[6, "combining-rows"]], "Command line options": [[2, "command-line-options"]], "Common filters": [[6, "common-filters"]], "Complexity and security validators": [[12, "complexity-and-security-validators"]], "Computed and Virtual fields": [[6, "computed-and-virtual-fields"]], "Computed fields": [[6, "computed-fields"]], "Connection failures (attempts parameter)": [[6, "connection-failures-attempts-parameter"]], "Connection pooling": [[6, "connection-pooling"]], "Connection strings (the uri parameter)": [[6, "connection-strings-the-uri-parameter"]], "Contents:": [[17, null]], "Convenience Decorators": [[5, "convenience-decorators"]], "Copy data from one db into another": [[6, "copy-data-from-one-db-into-another"]], "Copying the _scaffold app": [[4, "copying-the-scaffold-app"]], "Creating an app": [[4, null]], "Custom Action Buttons": [[14, "custom-action-buttons"]], "Custom Field types": [[6, "custom-field-types"]], "Custom columns": [[14, "custom-columns"]], "Custom fixtures": [[5, "custom-fixtures"]], "Custom forms": [[12, "custom-forms"]], "Custom helpers": [[10, "custom-helpers"]], "Custom widgets": [[12, "custom-widgets"]], "Customizing style": [[14, "customizing-style"]], "DAL constructor": [[6, "dal-constructor"]], "DAL introduction": [[6, "dal-introduction"]], "DAL signature": [[6, "dal-signature"]], "DIV": [[10, "div"]], "Data representation": [[6, "data-representation"]], "Database cascades": [[6, "database-cascades"]], "Database folder location": [[6, "database-folder-location"]], "Database quoting and case settings": [[6, "database-quoting-and-case-settings"]], "Database validators": [[12, "database-validators"]], "Date and time validators": [[12, "date-and-time-validators"]], "Debugging py4web with PyCharm": [[1, "debugging-py4web-with-pycharm"]], "Debugging py4web with VScode": [[1, "debugging-py4web-with-vscode"]], "Default migration settings": [[6, "default-migration-settings"]], "Default page layout": [[8, "default-page-layout"]], "Default values with coalesce and coalesce_zero": [[6, "default-values-with-coalesce-and-coalesce-zero"]], "Deployment on Docker/Podman": [[2, "deployment-on-docker-podman"]], "Deployment on GCloud (aka GAE - Google App Engine)": [[2, "deployment-on-gcloud-aka-gae-google-app-engine"]], "Deployment on PythonAnywhere.com": [[2, "deployment-on-pythonanywhere-com"]], "Deployment on Ubuntu": [[2, "deployment-on-ubuntu"]], "Dictionaries and tuples with IS_IN_SET": [[12, "dictionaries-and-tuples-with-is-in-set"]], "Distributed transaction": [[6, "distributed-transaction"]], "Domain-mapped apps": [[4, "domain-mapped-apps"]], "Dynamic Web Pages": [[4, "dynamic-web-pages"]], "EM": [[10, "em"]], "Experiment with the py4web shell": [[6, "experiment-with-the-py4web-shell"]], "Exporting and importing data": [[6, "exporting-and-importing-data"]], "Expressions": [[6, "expressions"]], "Extending using variables": [[8, "extending-using-variables"]], "FORM": [[10, "form"]], "Fetching a Row": [[6, "fetching-a-row"]], "Field constructor": [[6, "field-constructor"]], "Field types and validators": [[6, "field-types-and-validators"]], "File upload field": [[12, "file-upload-field"]], "First run": [[2, "first-run"]], "Fixing broken migrations": [[6, "fixing-broken-migrations"]], "Fixtures": [[5, null]], "Form structure manipulation": [[12, "form-structure-manipulation"]], "Form validation": [[12, "form-validation"]], "Forms": [[12, null]], "From scratch": [[4, "from-scratch"]], "From web2py to py4web": [[15, null]], "Generating raw SQL": [[6, "generating-raw-sql"]], "Google NoSQL (Datastore)": [[6, "google-nosql-datastore"]], "Google SQL": [[6, "google-sql"]], "Gotchas": [[6, "gotchas"]], "Grid": [[14, null]], "Grids with checkboxes": [[14, "grids-with-checkboxes"]], "Grouping and counting": [[6, "grouping-and-counting"]], "H1, H2, H3, H4, H5, H6": [[10, "h1-h2-h3-h4-h5-h6"]], "HEAD": [[10, "head"]], "HTML": [[10, "html"]], "HTML and XML (one Table at a time)": [[6, "html-and-xml-one-table-at-a-time"]], "HTTPS": [[2, "https"]], "Help, resources and hints": [[1, null]], "Helpers overview": [[10, "helpers-overview"]], "Hints and tips": [[1, "hints-and-tips"]], "How to contribute": [[1, "how-to-contribute"]], "I": [[10, "i"]], "IMG": [[10, "img"]], "INPUT": [[10, "input"]], "IS_ALPHANUMERIC": [[12, "is-alphanumeric"]], "IS_DATE": [[12, "is-date"]], "IS_DATETIME": [[12, "is-datetime"]], "IS_DATETIME_IN_RANGE": [[12, "is-datetime-in-range"]], "IS_DATE_IN_RANGE": [[12, "is-date-in-range"]], "IS_DECIMAL_IN_RANGE": [[12, "is-decimal-in-range"]], "IS_EMAIL": [[12, "is-email"]], "IS_EMPTY_OR": [[12, "is-empty-or"]], "IS_EQUAL_TO": [[12, "is-equal-to"]], "IS_EXPR": [[12, "is-expr"]], "IS_FILE": [[12, "is-file"]], "IS_FLOAT_IN_RANGE": [[12, "is-float-in-range"]], "IS_IMAGE": [[12, "is-image"]], "IS_INT_IN_RANGE": [[12, "is-int-in-range"]], "IS_IN_DB": [[12, "is-in-db"]], "IS_IN_DB and Tagging": [[12, "is-in-db-and-tagging"]], "IS_IN_SET": [[12, "is-in-set"]], "IS_IN_SET and Tagging": [[12, "is-in-set-and-tagging"]], "IS_IPADDRESS": [[12, "is-ipaddress"]], "IS_IPV4": [[12, "is-ipv4"]], "IS_IPV6": [[12, "is-ipv6"]], "IS_JSON": [[12, "is-json"]], "IS_LENGTH": [[12, "is-length"]], "IS_LIST_OF": [[12, "is-list-of"]], "IS_LIST_OF_EMAILS": [[12, "is-list-of-emails"]], "IS_LOWER": [[12, "is-lower"]], "IS_MATCH": [[12, "is-match"]], "IS_NOT_EMPTY": [[12, "is-not-empty"]], "IS_NOT_IN_DB": [[12, "is-not-in-db"]], "IS_NULL_OR": [[12, "is-null-or"]], "IS_SAFE": [[12, "is-safe"]], "IS_SLUG": [[12, "is-slug"]], "IS_STRONG": [[12, "is-strong"]], "IS_TIME": [[12, "is-time"]], "IS_UPLOAD_FILENAME": [[12, "is-upload-filename"]], "IS_UPPER": [[12, "is-upper"]], "IS_URL": [[12, "is-url"]], "Indexes": [[6, "indexes"]], "Indices and tables": [[17, "indices-and-tables"]], "Information workflow": [[8, "information-workflow"]], "Inner join": [[6, "inner-join"]], "Inserting and updating from a dictionary": [[6, "inserting-and-updating-from-a-dictionary"]], "Installation and Startup": [[2, null]], "Installing from binaries": [[2, "installing-from-binaries"]], "Installing from pip, using a virtual environment": [[2, "installing-from-pip-using-a-virtual-environment"]], "Installing from pip, without virtual environment": [[2, "installing-from-pip-without-virtual-environment"]], "Installing from source (globally)": [[2, "installing-from-source-globally"]], "Installing from source (locally)": [[2, "installing-from-source-locally"]], "Internationalization": [[11, null]], "Joins and Relations": [[6, "joins-and-relations"]], "Key features": [[14, "key-features"]], "LABEL": [[10, "label"]], "LDAP": [[13, "ldap"]], "LI": [[10, "li"]], "Lazy Tables": [[6, "lazy-tables"]], "Left outer join": [[6, "left-outer-join"]], "Legacy databases and keyed tables": [[6, "legacy-databases-and-keyed-tables"]], "Logical operators": [[6, "logical-operators"]], "Login into the Dashboard": [[3, "login-into-the-dashboard"]], "MSSQL (Microsoft SQL Server)": [[6, "mssql-microsoft-sql-server"]], "Making a secure connection": [[6, "making-a-secure-connection"]], "Many to many relation": [[6, "many-to-many-relation"]], "Migration control summary": [[6, "migration-control-summary"]], "Migrations": [[6, "migrations"]], "Mobile development": [[8, "mobile-development"]], "Model-less applications": [[6, "model-less-applications"]], "More on uploads": [[6, "more-on-uploads"]], "Multiple Tags objects": [[13, "multiple-tags-objects"]], "Multiple fixtures": [[5, "multiple-fixtures"]], "MySQL": [[6, "mysql"]], "New style virtual fields (experimental)": [[6, "new-style-virtual-fields-experimental"]], "Note on new DAL and adapters": [[6, "note-on-new-dal-and-adapters"]], "OAuth2 with Discord": [[13, "oauth2-with-discord"]], "OAuth2 with Facebook": [[13, "oauth2-with-facebook"]], "OAuth2 with Google": [[13, "oauth2-with-google"]], "OL": [[10, "ol"]], "OPTION": [[10, "option"]], "Old style virtual fields": [[6, "old-style-virtual-fields"]], "On return values": [[4, "on-return-values"]], "One to many relation": [[6, "one-to-many-relation"]], "Oracle": [[6, "oracle"]], "Other DAL constructor parameters": [[6, "other-dal-constructor-parameters"]], "Other operators": [[6, "other-operators"]], "Other validators": [[12, "other-validators"]], "P": [[10, "p"]], "PAM": [[13, "pam"]], "PRE": [[10, "pre"]], "Page layout standard structure": [[8, "page-layout-standard-structure"]], "Pluralize": [[11, "pluralize"]], "Prerequisites": [[1, "prerequisites"]], "Query, Set, Rows": [[6, "query-set-rows"]], "Range, set and equality validators": [[12, "range-set-and-equality-validators"]], "Raw SQL": [[6, "raw-sql"]], "Record versioning": [[6, "record-versioning"]], "Recursive selects": [[6, "recursive-selects"]], "Reference Fields": [[14, "reference-fields"]], "Rendering rows using represent": [[6, "rendering-rows-using-represent"]], "Replicated databases": [[6, "replicated-databases"]], "Reserved keywords": [[6, "reserved-keywords"]], "Resources": [[1, "resources"]], "RestAPI GET": [[7, "restapi-get"]], "RestAPI policies and actions": [[7, "restapi-policies-and-actions"]], "RestAPI practical examples": [[7, "restapi-practical-examples"]], "Routes": [[4, "routes"]], "Run-time field and table modification": [[6, "run-time-field-and-table-modification"]], "SCRIPT": [[10, "script"]], "SELECT": [[10, "select"]], "SPAN": [[10, "span"]], "SQLite": [[6, "sqlite"]], "STYLE": [[10, "style"]], "Sample Action Button Class": [[14, "sample-action-button-class"]], "Searching and filtering": [[14, "searching-and-filtering"]], "Self-Reference and aliases": [[6, "self-reference-and-aliases"]], "Sending messages using a background task": [[16, "sending-messages-using-a-background-task"]], "Server-side DOM": [[10, "server-side-dom"]], "Server-side session anywhere": [[5, "server-side-session-anywhere"]], "Server-side session in Redis": [[5, "server-side-session-in-redis"]], "Server-side session in database": [[5, "server-side-session-in-database"]], "Server-side session in memcache": [[5, "server-side-session-in-memcache"]], "Setup procedures": [[2, "setup-procedures"]], "Sharing sessions": [[5, "sharing-sessions"]], "Shortcuts": [[6, "shortcuts"]], "Simple conversion examples": [[15, "simple-conversion-examples"]], "Sorted options": [[12, "sorted-options"]], "Special installations": [[2, "special-installations"]], "Special type validators": [[12, "special-type-validators"]], "Standard widgets": [[12, "standard-widgets"]], "Static web pages": [[4, "static-web-pages"]], "Substrings": [[6, "substrings"]], "Supported databases": [[6, "supported-databases"]], "Supported platforms and prerequisites": [[2, "supported-platforms-and-prerequisites"]], "TABLE, TR, TD": [[10, "table-tr-td"]], "TAG": [[10, "tag"]], "TBODY": [[10, "tbody"]], "TEXTAREA": [[10, "textarea"]], "TH": [[10, "th"]], "THEAD": [[10, "thead"]], "TITLE": [[10, "title"]], "TT": [[10, "tt"]], "Table constructor": [[6, "table-constructor"]], "Table inheritance": [[6, "table-inheritance"]], "Table methods": [[6, "table-methods"]], "Tagging records": [[6, "tagging-records"]], "Tags and Permissions": [[13, "tags-and-permissions"]], "Template Functions": [[8, "template-functions"]], "Templates": [[4, "templates"]], "Text format validators": [[12, "text-format-validators"]], "The Auth fixture": [[5, "the-auth-fixture"]], "The Condition fixture": [[5, "the-condition-fixture"]], "The DAL fixture": [[5, "the-dal-fixture"]], "The DAL: a quick tour": [[6, "the-dal-a-quick-tour"]], "The Dashboard": [[3, null]], "The Database Abstraction Layer (DAL)": [[6, null]], "The Discord server": [[1, "the-discord-server"]], "The Flash fixture": [[5, "the-flash-fixture"]], "The Form constructor": [[12, "the-form-constructor"]], "The Google group": [[1, "the-google-group"]], "The Grid object": [[14, "the-grid-object"]], "The Inject fixture": [[5, "the-inject-fixture"]], "The Q object": [[16, "the-q-object"]], "The RestAPI": [[7, null]], "The RestAPI response": [[7, "the-restapi-response"]], "The Session fixture": [[5, "the-session-fixture"]], "The T object": [[16, "the-t-object"]], "The Template fixture": [[5, "the-template-fixture"]], "The Translator fixture": [[5, "the-translator-fixture"]], "The URLsigner fixture": [[5, "the-urlsigner-fixture"]], "The _scaffold app": [[4, "the-scaffold-app"]], "The main Web page": [[3, "the-main-web-page"]], "The request object": [[4, "the-request-object"]], "The scheduler": [[16, "the-scheduler"]], "The sidecar parameter": [[12, "the-sidecar-parameter"]], "The sources on GitHub": [[1, "the-sources-on-github"]], "This manual": [[1, "this-manual"]], "Timing queries": [[6, "timing-queries"]], "Tutorials and video": [[1, "tutorials-and-video"]], "Two Factor Authentication": [[13, "two-factor-authentication"]], "UL": [[10, "ul"]], "URL": [[10, "url"]], "Understanding the design": [[2, "understanding-the-design"]], "Update the translation files": [[11, "update-the-translation-files"]], "Upgrading": [[2, "upgrading"]], "User Impersonation": [[13, "user-impersonation"]], "Using Auth inside actions": [[13, "using-auth-inside-actions"]], "Using DAL without define tables": [[6, "using-dal-without-define-tables"]], "Using Fixtures": [[5, "using-fixtures"]], "Using Inject": [[10, "using-inject"]], "Using an iterator-based select for lower memory use": [[6, "using-an-iterator-based-select-for-lower-memory-use"]], "Using callable parameters": [[14, "using-callable-parameters"]], "Using templates": [[14, "using-templates"]], "Using the DAL \u201cstand-alone\u201d": [[6, "using-the-dal-stand-alone"]], "Using the dashboard app with databases": [[6, "using-the-dashboard-app-with-databases"]], "Validation functions": [[12, "validation-functions"]], "Virtual fields": [[6, "virtual-fields"]], "WSGI": [[2, "wsgi"]], "Watch for files change": [[4, "watch-for-files-change"]], "What is py4web?": [[0, null]], "Widgets": [[12, "widgets"]], "XML": [[10, "xml"]], "YATL Template Language": [[8, null]], "YATL helpers": [[10, null]], "_lastsql": [[6, "lastsql"]], "as_dict and as_list": [[6, "as-dict-and-as-list"]], "belongs": [[6, "belongs"]], "block and super": [[8, "block-and-super"]], "cache, cacheable": [[6, "cache-cacheable"]], "call command option": [[2, "call-command-option"]], "callbacks on record insert, delete and update": [[6, "callbacks-on-record-insert-delete-and-update"]], "case": [[6, "case"]], "children": [[10, "children"]], "commit and rollback": [[6, "commit-and-rollback"]], "count, isempty, delete, update": [[6, "count-isempty-delete-update"]], "def...return": [[8, "def-return"]], "define_table signature": [[6, "define-table-signature"]], "distinct": [[6, "distinct"]], "drop": [[6, "drop"]], "executesql": [[6, "executesql"]], "extend and include": [[8, "extend-and-include"]], "filter_in and filter_out": [[6, "filter-in-and-filter-out"]], "find": [[10, "find"]], "find, exclude, sort": [[6, "find-exclude-sort"]], "first and last": [[6, "first-and-last"]], "for...in": [[8, "for-in"]], "format: Record representation": [[6, "format-record-representation"]], "groupby, having": [[6, "groupby-having"]], "htmx": [[16, "htmx"]], "htmx usage in Form": [[16, "htmx-usage-in-form"]], "htmx usage in Grid": [[16, "htmx-usage-in-grid"]], "id: Notes about the primary key": [[6, "id-notes-about-the-primary-key"]], "if...elif...else": [[8, "if-elif-else"]], "insert": [[6, "insert"]], "join, left": [[6, "join-left"]], "like, ilike, regexp, startswith, endswith, contains, upper, lower": [[6, "like-ilike-regexp-startswith-endswith-contains-upper-lower"]], "limitby": [[6, "limitby"]], "list: and contains": [[6, "list-type-and-contains"]], "migrate, fake_migrate": [[6, "migrate-fake-migrate"]], "new_app command option": [[2, "new-app-command-option"]], "on_define": [[6, "on-define"]], "orderby": [[6, "orderby"]], "orderby, groupby, limitby, distinct, having, orderby_on_limitby, join, left, cache": [[6, "orderby-groupby-limitby-distinct-having-orderby-on-limitby-join-left-cache"]], "orderby_on_limitby": [[6, "orderby-on-limitby"]], "plural and singular": [[6, "plural-and-singular"]], "polymodel": [[6, "polymodel"]], "primarykey: Support for legacy tables": [[6, "primarykey-support-for-legacy-tables"]], "py4web and asyncio": [[16, "py4web-and-asyncio"]], "py4web model": [[6, "py4web-model"]], "py4web: the reference Manual": [[17, null]], "redefine": [[6, "redefine"]], "rname: Real name": [[6, "rname-real-name"]], "run command option": [[2, "run-command-option"]], "select command": [[6, "select-command"]], "sequence_name": [[6, "sequence-name"]], "set_password command option": [[2, "set-password-command-option"]], "setup command option": [[2, "setup-command-option"]], "shell command option": [[2, "shell-command-option"]], "string.format": [[16, "string-format"]], "sum, avg, min, max and len": [[6, "sum-avg-min-max-and-len"]], "table_class": [[6, "table-class"]], "trigger_name": [[6, "trigger-name"]], "try...except...else...finally": [[8, "try-except-else-finally"]], "two_factor_required": [[13, "two-factor-required"]], "two_factor_send": [[13, "two-factor-send"]], "two_factor_tries": [[13, "two-factor-tries"]], "two_factor_validate": [[13, "two-factor-validate"]], "update_or_insert": [[6, "update-or-insert"]], "update_record": [[6, "update-record"]], "utils.js": [[16, "utils-js"]], "validate_and_insert, validate_and_update": [[6, "validate-and-insert-validate-and-update"]], "version command option": [[2, "version-command-option"]], "while": [[8, "while"]], "year, month, day, hour, minutes, seconds": [[6, "year-month-day-hour-minutes-seconds"]], "\u201cAccessing OS files\u201d example": [[15, "accessing-os-files-example"]], "\u201cForm and flash\u201d example": [[15, "form-and-flash-example"]], "\u201cHello world\u201d example": [[15, "hello-world-example"]], "\u201cRedirect with variables\u201d example": [[15, "redirect-with-variables-example"]], "\u201cReturn calling methods\u201d example": [[15, "return-calling-methods-example"]], "\u201cReturning args\u201d example": [[15, "returning-args-example"]], "\u201cReturning variables\u201d example": [[15, "returning-variables-example"]], "\u201cSetting up a counter\u201d example": [[15, "setting-up-a-counter-example"]], "\u201cView\u201d example": [[15, "view-example"]], "\u201cauth\u201d example": [[15, "auth-example"]], "\u201cgrid\u201d example": [[15, "grid-example"]]}, "docnames": ["chapter-01", "chapter-02", "chapter-03", "chapter-04", "chapter-05", "chapter-06", "chapter-07", "chapter-08", "chapter-09", "chapter-1", "chapter-10", "chapter-11", "chapter-12", "chapter-13", "chapter-14", "chapter-15", "chapter-16", "index"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["chapter-01.rst", "chapter-02.rst", "chapter-03.rst", "chapter-04.rst", "chapter-05.rst", "chapter-06.rst", "chapter-07.rst", "chapter-08.rst", "chapter-09.rst", "chapter-1.rst", "chapter-10.rst", "chapter-11.rst", "chapter-12.rst", "chapter-13.rst", "chapter-14.rst", "chapter-15.rst", "chapter-16.rst", "index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16], "0": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "00": 7, "01": 7, "02": [6, 12], "03": [2, 6, 7], "04": 2, "04t07": 7, "05": 7, "08": 12, "0x4e86": 12, "0x7fa533ff7640": 10, "1": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 14, 15, 16], "10": [2, 6, 7, 8, 10, 11, 12, 13, 16], "100": [2, 6, 7, 8, 12, 16], "1000": [5, 6, 12, 16], "1000m": 16, "100px": 16, "1024": 12, "1048576": 12, "10px": 14, "11": [6, 12], "111111": 16, "11211": 5, "12": 12, "120": 6, "123": [10, 12], "123218": 7, "123456": 12, "125": 6, "127": [2, 3, 4, 5, 12, 14], "13": [6, 12], "132635": 7, "14": [8, 12, 14], "15": [6, 12, 13, 14], "16": 12, "168": 12, "169": 12, "16px": 8, "172": [6, 12], "174": 6, "178974": 7, "19": [6, 7, 12], "192": [6, 12], "1963": 12, "198": 6, "199": 12, "19t05": 7, "1e100": 6, "1kb": 12, "1l": 6, "1mb": 12, "1pkogiy59xj8co8": 8, "2": [5, 6, 7, 8, 10, 11, 12, 13, 15, 16], "20": [2, 7, 11, 12], "200": [7, 12, 15, 16], "2001": 12, "2002": 12, "2005": 6, "2007": 0, "2008": 12, "2009": 12, "200m": 16, "200x200": 12, "2010": 6, "2012": 6, "2013": 6, "2015": 0, "2018": 6, "2019": 7, "201988": 7, "2020": 1, "20201112": 3, "2021": 7, "2022": 5, "207": 6, "217": 6, "22": 13, "227": 6, "239": 6, "2396": 12, "24": 12, "254": 12, "255": 12, "256": 6, "2616": 12, "28": 12, "2em": 16, "2px": 16, "3": [0, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "30": [2, 6, 8, 12], "301": 15, "309903": 7, "31": [6, 7, 12], "32": [6, 12], "322494": 7, "32768": 6, "33": [6, 12], "34": 7, "3490": 12, "3492": 12, "35": 6, "355181": 7, "3600": [5, 6], "366288": 7, "38": 7, "3em": 16, "4": [2, 5, 6, 7, 8, 12, 14], "40": [2, 10, 16], "400": [5, 15], "401": 13, "404": [5, 13], "405515": 7, "43": 6, "45": [8, 12], "451907": 7, "453020": 7, "456": 12, "466030": 7, "4e": 12, "5": [5, 6, 7, 8, 11, 12, 13, 14, 16], "50": [2, 7], "500": 16, "500m": 16, "512": 6, "53": 12, "54": 8, "559918": 7, "58": 8, "59": 12, "6": [5, 6, 7, 12, 13, 15, 16], "60": [5, 6], "63": [6, 12], "6379": 5, "64": [6, 8, 16], "65": 6, "6to4": 12, "7": [2, 7, 8, 12, 15], "70": 7, "74": 6, "75": 7, "8": [2, 6, 7, 12], "80": [4, 7, 12], "8000": [2, 3, 4, 12, 14], "86": 12, "8601": 7, "8em": [8, 16], "9": [6, 7, 12, 14, 15], "90": [6, 7], "91": 6, "95": 6, "97": 6, "974953": 7, "99": 6, "A": [2, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17], "AND": 6, "AS": 6, "And": [1, 4, 5, 6, 13, 14, 16], "As": [4, 5, 6, 7, 8, 10, 12, 13, 14, 16], "At": [6, 8], "Be": [5, 6, 8], "But": [1, 2, 4, 5, 6, 8, 10, 12, 14, 16], "By": [2, 4, 5, 6, 12, 13, 15], "For": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "IF": 6, "IN": 6, "INTO": 6, "IT": 11, "If": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "In": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "It": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 16], "Its": [0, 2, 6, 12, 14], "NO": 6, "NOT": [6, 12, 13], "No": [2, 6], "Not": 6, "ON": [6, 12], "OR": [6, 10, 12, 14], "Of": 8, "On": [2, 6, 13, 16], "One": [2, 4, 12, 13], "Or": [5, 12], "Such": 4, "That": [4, 6, 15], "The": [0, 2, 8, 10, 11, 13, 15, 17], "Their": [5, 6, 15], "Then": [4, 5, 6, 10, 12, 13, 14, 16], "There": [1, 2, 5, 6, 8, 12, 13, 14, 16], "These": [0, 5, 6, 10, 12, 14], "To": [2, 4, 6, 8, 10, 11, 12, 13, 14, 16], "With": [2, 6, 12, 13, 16], "_": [2, 5, 12, 16], "__": 4, "__dict__": 5, "__file__": [5, 7, 14], "__init__": [2, 4, 5, 6, 7, 13, 14, 15, 16], "__prerequisite__": 5, "__prerequisites__": [5, 13], "__str__": [5, 8, 10], "_action": [10, 14], "_adapt": 6, "_after_delet": 6, "_after_insert": 6, "_after_upd": 6, "_alt": 10, "_always_": 13, "_and": 12, "_autocomplet": 16, "_autocomplete_search_field": 16, "_before_": 6, "_before_delet": 6, "_before_insert": 6, "_before_upd": 6, "_bgcolor": 10, "_c": 10, "_check": 10, "_class": [5, 10, 12, 16], "_col": 10, "_common_filt": 6, "_count": 6, "_dashboard": [2, 3], "_data": 10, "_db": 6, "_dbname": 6, "_default": [2, 4], "_delet": 6, "_disabl": 10, "_document": [1, 3], "_enable_record_vers": 6, "_extra": 6, "_format": 6, "_href": [5, 8, 10, 12, 14], "_hx": 16, "_id": [6, 10, 12, 16], "_insert": 6, "_listifi": 6, "_method": [10, 14], "_name": [6, 10, 12, 14, 16], "_next_url": 13, "_nonreserv": 6, "_onclick": 12, "_placehold": [12, 16], "_row": 10, "_scaffold": [5, 8, 10, 12, 13, 14, 15, 17], "_search": 16, "_select": [6, 10], "_sesson": 5, "_src": 10, "_style": [12, 16], "_tabl": [6, 12, 16], "_tablenam": 6, "_tag_": 13, "_time": 6, "_titl": [12, 16], "_type": [6, 10, 12, 14, 16], "_u": 10, "_updat": 6, "_uri": 6, "_valu": [10, 12, 14, 16], "_xmln": 10, "aaabaaeaaqeaaaeaiaawaaaafgaaacgaaaabaaaaagaaaaeaiaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaapaaaaaa": 8, "ab": [10, 12], "abbrevi": 12, "abc": [10, 12], "abil": 15, "abl": [1, 2, 5, 6, 12], "abort": [4, 6], "about": [0, 7, 13, 14, 15, 16, 17], "abov": [4, 5, 6, 7, 8, 12, 13, 14, 16], "abracadabra": 2, "absent": 2, "absolut": [2, 6, 15], "abspath": 4, "abstract": [5, 15, 17], "accdesc": 6, "accept": [1, 2, 4, 5, 6, 10, 11, 12, 13, 15, 16], "access": [0, 2, 4, 5, 6, 10, 12, 13, 16], "accnum": 6, "accomplish": [5, 6, 13, 15], "accord": [6, 8], "accordingli": 8, "account": [2, 6], "acctyp": 6, "achiev": [5, 6, 15], "acknowledg": 17, "acquir": 6, "across": [6, 8], "act": [6, 10, 12], "action": [4, 5, 6, 10, 12, 15, 16, 17], "action_button": 14, "action_token": [5, 13], "activ": [2, 6, 13], "actual": [2, 5, 6, 8, 14], "ad": [0, 2, 5, 11, 12, 13, 14, 16], "adapt": [13, 16], "adapter_arg": 6, "add": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "addit": [2, 4, 5, 6, 7, 8, 12, 14], "addition": 14, "additional_class": 14, "additional_styl": 14, "address": [2, 6, 12, 13], "admin": [0, 6, 15], "administr": 2, "advanc": [2, 13, 14, 17], "advantag": [2, 6, 8, 16], "advis": [6, 14], "affect": 6, "after": [2, 3, 5, 6, 8, 12, 13, 14, 16], "after_connect": 6, "after_delet": 6, "after_insert": 6, "after_upd": 6, "aftermath": 6, "ag": 6, "again": [2, 3, 4, 5, 6, 12, 13, 14, 16], "against": [6, 7, 10, 12, 13, 14], "aggreg": 6, "agnost": 4, "aid": 6, "aim": 0, "ajax": [8, 14, 16], "aka": 1, "alchemi": 6, "alert": [5, 8, 10, 12, 15], "alex": 6, "alfaro": [0, 1], "alg": 12, "algorithm": [5, 12], "alia": [6, 12], "aliv": 16, "all": [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16], "alloc": 12, "allow": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 16], "allowed_act": 13, "allowed_attribut": 10, "allowed_overrid": 12, "allowed_pattern": 7, "allowed_schem": 12, "almost": 5, "along": [2, 4, 10, 12, 16], "alphabet": 12, "alphanumer": 12, "alreadi": [2, 3, 4, 5, 6, 10, 12, 13, 14], "also": [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "alt": 10, "alter": 6, "altern": [2, 5, 6, 8, 12, 16], "although": [2, 6, 8, 12], "alwai": [0, 2, 4, 5, 6, 8, 12, 16], "am": [12, 16], "amazon": 6, "ambigu": [4, 6], "among": [6, 12], "amount": 6, "an": [0, 1, 2, 3, 5, 8, 10, 12, 13, 14, 15, 16, 17], "analogi": 15, "ancestor": 10, "anchor": 16, "andrew": 1, "angl": 8, "angular": [0, 16], "ani": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "annoi": 2, "anonym": [6, 8, 12], "anoth": [4, 5, 8, 11, 12, 13], "anotherpath": 5, "ansi": 6, "answer": 1, "anyhow": 2, "anyobj": 6, "anyth": 4, "anywai": 6, "anywher": [8, 15], "api": [0, 6, 7, 10, 12, 13, 15, 16], "api_kei": 16, "api_vers": 7, "app": [0, 1, 3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17], "app1": 5, "app1_sess": 5, "app2": 5, "app_fold": 15, "app_nam": [2, 4, 5, 6], "app_watch_handl": 4, "appadmin": [0, 6], "appar": 6, "appear": [6, 12, 14], "append": [5, 6, 10, 12, 14, 16], "append_id": 14, "appl": 12, "appli": [0, 5, 6, 12, 13, 14, 16], "applic": [0, 2, 3, 4, 5, 8, 12, 13, 14, 15, 16], "appnam": [2, 4, 5, 13, 15], "appname_sess": 5, "approach": [6, 15], "appropri": [6, 12], "approv": 13, "apps_fold": 2, "apt": 13, "ar": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "arbitrari": [4, 6, 13], "archiv": 6, "archive_db": 6, "archive_nam": 6, "aren": 6, "arg": [1, 2, 6], "argument": [2, 4, 5, 6, 8, 10, 12, 13, 15, 16], "arithmet": 12, "around": [1, 6, 15], "arrai": [10, 16], "arrang": [6, 12], "arriv": [0, 6], "as_ordered_dict": 6, "ascii": 12, "asid": [6, 16], "ask": [2, 5, 6, 13, 16], "aspect": 4, "assert": [6, 16], "asset": 2, "assign": [6, 8, 12, 13, 15, 16], "assist": 1, "associ": [6, 11, 12, 13], "assum": [2, 5, 6, 7, 11, 12, 15], "async": 16, "asynchron": 16, "asyncio": 17, "attach": [5, 6, 13, 15], "attack": [5, 10], "attempt": [12, 13], "attent": [4, 6], "attr": [12, 14, 16], "attribut": [4, 5, 10, 12, 14, 15, 16], "attributes_plugin": 16, "attributespluginhtmx": 16, "aug": 12, "august": 12, "auth": [0, 2, 4, 6, 8, 10, 16, 17], "auth_group": 13, "auth_plugin": 13, "auth_us": [5, 6, 13, 16], "auth_user_tag_group": [6, 13], "auth_user_tagged_group": 13, "authent": [5, 6, 14, 17], "author": [5, 6, 7, 17], "auto": [6, 14], "auto_import": 6, "auto_process": [14, 16], "autocomplete_queri": 16, "autodelet": 6, "autogener": 6, "automat": [2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "avail": [1, 2, 6, 8, 10, 13, 15, 16], "averag": 6, "avoid": [0, 1, 2, 5, 6, 8, 12], "awai": 6, "awar": 6, "awesom": [8, 14], "axel": 0, "axolotl": 0, "b": [5, 6, 8, 10, 12, 15, 16], "back": [5, 6, 12], "backend": [4, 6], "background": [2, 4, 12, 17], "backport": 6, "backslash": 4, "backup": [2, 6, 12], "backward": [0, 6, 12], "bad_dai": 6, "bail": 6, "banana": 12, "bar": [8, 14], "barrier": 0, "base": [0, 2, 3, 4, 5, 7, 8, 12, 13, 14, 15, 16], "base64": [6, 8, 16], "base_dn": 13, "baseadapt": 6, "bash": 2, "basic": [0, 1, 2, 5, 6, 16, 17], "bat": 2, "batman": [7, 12, 14], "battl": 0, "beaslei": 0, "becaus": [0, 2, 4, 5, 6, 8, 10, 11, 12, 13, 14, 16], "becom": [12, 16], "bed": 11, "been": [0, 2, 5, 6, 8, 12, 13], "befor": [1, 2, 5, 6, 8, 10, 12, 13, 14], "before_delet": 6, "before_insert": 6, "before_upd": 6, "begin": [8, 12, 13, 14], "beginn": 2, "behav": [6, 14], "behavior": [4, 5, 6, 10, 12, 15, 16], "behaviour": [2, 6, 14], "being": [1, 4, 5, 6, 7, 8, 12, 16], "believ": [0, 5], "belong": [13, 15], "below": [5, 6, 7, 12, 14], "benefit": [6, 16], "best": [1, 2, 5, 6, 11, 14], "better": [0, 1, 2, 4, 6, 14, 15, 16], "between": [5, 6, 10, 12, 15], "beyond": 12, "bgcolor": 10, "big": [6, 15], "bigint": 6, "bigint_id": 6, "bin": [1, 2], "binari": 6, "bind": 16, "birthplac": 6, "bit": 12, "bitbucket": 1, "black": [8, 12], "blank": [12, 14], "blink": 16, "blob": 6, "block": [4, 10, 13, 16], "blockquot": 10, "blog": [1, 6, 10], "blog_post": 6, "blue": [4, 6, 12, 15], "bmp": 12, "boat": 6, "bob": 6, "bodi": [4, 8, 13, 14, 16], "boilerpl": 5, "bold": 10, "book": [1, 6], "boolean": [6, 14], "boost": [6, 8], "bootstrap": 14, "border": 16, "born": 6, "botaro": 0, "both": [1, 2, 4, 5, 6, 8, 12, 15], "bottl": [0, 4, 5, 8, 15], "bottle_app": 2, "bottleneck": 16, "bottlepi": [4, 5], "bottom": 12, "boundari": 12, "box": [0, 6, 12], "br": [8, 10], "bracket": [0, 8, 12], "branch": [1, 2], "break": [2, 6, 12], "breez": 1, "briefli": 2, "broke": 0, "broken": [0, 12], "brows": [2, 3, 14, 16], "browser": [1, 2, 3, 5, 7, 8, 14, 16], "bruce": [7, 14], "bsd": 1, "buffer": [4, 6], "bug": [1, 3, 6], "build": [1, 2, 4, 5, 6, 10, 12, 14, 16], "builder": 16, "built": [0, 1, 4, 6, 8, 12, 14, 16, 17], "bulk_insert": 6, "bulletproof": 6, "bulma": [12, 14, 16], "bunch": 2, "buse": 16, "button": [3, 4, 5, 10, 12, 16, 17], "bypass": [12, 13, 14], "byte": [6, 12], "bytecod": 8, "c": [1, 2, 3, 5, 6, 8, 10, 12, 15], "ca": 12, "cach": [0, 2, 4, 12, 17], "cache_db_select": 6, "calcul": 6, "calendar": 14, "call": [0, 1, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16], "callabl": [10, 12], "callback": [12, 13, 16], "callback_url": 13, "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "cancel": [12, 16], "cancel_attr": 16, "cane": [11, 16], "cani": [11, 16], "cannot": [2, 5, 6, 8, 10, 12, 16], "capabl": [0, 14, 16], "capit": 16, "captur": 16, "car": 6, "card": 6, "care": [5, 6, 8, 15, 16], "carl": 6, "carri": 15, "carrol": 0, "cart": 5, "case": [0, 1, 2, 3, 4, 5, 8, 10, 12, 13, 14, 15, 16], "case_sensit": 6, "cassio": 0, "catch": 6, "caus": [6, 12], "caveat": [6, 8, 15, 17], "cd": 2, "cdnj": [8, 14], "celeri": 17, "center": [5, 8], "centuri": 12, "ceo": [7, 14], "certain": [6, 8, 12, 14], "certif": 2, "cf": 4, "cgi": 12, "chair": 6, "challeng": 13, "chang": [0, 1, 2, 3, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17], "change_email": 13, "change_password": [8, 13], "changed_fil": 4, "channel": 1, "chapter": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15], "char": [6, 13], "charact": [4, 6, 10, 12], "charg": [2, 15], "chat": 1, "check": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "check_": 16, "check_reserv": 6, "checkbox": [8, 10, 17], "checkboxwidget": 12, "cherri": 12, "chicago": 6, "children": 14, "choic": [1, 4, 12, 14], "choos": [1, 6, 12, 13], "choosen": 6, "chose": 4, "chosen": 6, "chrome": [3, 16], "circular": 6, "circumst": 5, "cit0801": 7, "cit0802": 7, "cit1601": 16, "cite": [10, 12], "clark": [7, 14], "clash": 12, "class": [5, 6, 8, 10, 12, 13, 15, 16], "class_inner_except": 16, "class_styl": 14, "claudia": 6, "claus": [6, 8], "clean": [1, 10], "cleaner": 5, "cleanup": 0, "clear": [6, 12], "clearli": [6, 16], "clever": 12, "cli": [1, 2], "click": [3, 6, 10, 12, 14, 16], "clickabl": 14, "client": [0, 7, 12, 13, 16], "client_id": 13, "client_ip": 5, "client_secret": 13, "clientsid": 16, "clock": 12, "clone": [2, 4, 6, 16], "close": [5, 6, 8, 10], "closer": 6, "cloudflar": [8, 14], "cmd": 2, "cn": 13, "co": [6, 12], "coa": 6, "code": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "col": 10, "collaps": [7, 10], "collat": 6, "collect": [0, 2, 15], "colnam": 6, "colon": 8, "color": [4, 6, 8, 10, 12, 16], "colspan": 10, "column": [6, 17], "com": [0, 1, 3, 4, 5, 8, 10, 13, 14, 16], "combin": [5, 10, 12, 14], "come": [0, 4, 5, 6, 8, 12, 13, 14, 15, 16], "comma": [2, 6, 16], "command": [3, 4, 8, 10, 17], "comment": [6, 10], "commit": [4, 5, 7, 14, 15, 16], "common": [2, 3, 4, 5, 8, 12, 13, 15, 16], "common_filt": 6, "commun": [0, 5, 6], "compact": 6, "compani": 14, "compar": [0, 6, 13, 15], "comparison": [6, 12, 13], "compat": [0, 1, 6, 10, 12], "competitor": 0, "compil": [4, 8, 10, 12], "compiled_css": 4, "complain": 6, "complet": [0, 6, 8, 10, 12, 13, 16], "complex": [1, 2, 4, 5, 6, 8, 10, 13, 15, 16], "compliant": [6, 16], "complic": 5, "compon": [0, 3, 4, 5, 8, 10, 12, 13, 16], "component_1": 16, "compos": [2, 5], "compound": 10, "compress": [3, 4], "compris": 3, "compromis": 0, "comput": [1, 16, 17], "concaten": [6, 10], "conceiv": 6, "concept": [0, 13], "concern": 6, "concurr": [0, 2, 6, 12, 16], "cond": 5, "condit": [6, 8, 12, 13, 17], "condition": 6, "config": [2, 4], "configur": [1, 2, 4, 5, 6, 12, 13, 15, 16], "configuraiton": 4, "confirm": [2, 12, 13, 14, 16], "conflict": [0, 2, 4, 6], "confus": 6, "conjunct": 6, "conn": 5, "connect": [3, 4, 5, 12, 15], "connectionpool": 6, "consequ": [6, 12, 16], "consid": [5, 6, 8, 10, 12, 14, 15, 16], "consider": 6, "consist": [5, 6, 8, 14, 15], "consol": [2, 10, 13, 16], "constant": 12, "constrain": 15, "constraint": [0, 6], "construct": [4, 6], "constructor": [5, 10, 13, 17], "consult": 6, "contain": [0, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16], "contect": 8, "content": [2, 4, 5, 6, 8, 10, 12, 14, 15, 16], "content_typ": 16, "context": [3, 5, 6, 8, 13], "contextlib": 6, "continu": [6, 8, 16], "contribut": [0, 17], "control": [2, 4, 5, 8, 10, 12, 13, 14, 15, 16], "conveni": [6, 17], "convent": [4, 6, 8, 12, 15], "convers": [12, 17], "convert": [1, 4, 6, 10, 12], "cooki": [0, 4, 6, 12, 16], "copi": [1, 2, 3, 8, 10, 14, 17], "copyfileobj": 6, "core": [1, 2, 4, 5], "corei": 1, "corner": 14, "cornerston": 0, "correct": [1, 4, 6, 13], "correct_cod": 13, "correctli": [4, 12], "correspond": [2, 3, 4, 6, 10, 11, 13, 14, 15], "corrupt": 6, "couchdb": 6, "couchdbadapt": 6, "could": [1, 2, 4, 6, 8, 10, 12, 13, 15], "count": [5, 7, 13, 14, 16], "counter": [5, 6, 16], "counterpart": 4, "coupl": [14, 16], "cours": [1, 8], "cover": 4, "cp": 2, "creat": [0, 1, 2, 3, 5, 6, 8, 10, 12, 13, 14, 15, 16, 17], "create_form": 12, "create_th": 12, "created_bi": 6, "created_on": 6, "creation": [2, 6], "creativ": [12, 13], "credential_decod": 6, "criteria": [6, 12], "critic": 2, "cross": [5, 6, 10], "crossorigin": 8, "crt": [2, 6], "crud": [3, 12, 16], "cruz": 1, "crypt": 2, "csrf": [5, 12], "csrf_protect": 12, "csrf_session": 12, "css": [1, 4, 5, 8, 10, 12, 13, 14, 16], "csv": 15, "ct": 5, "ctrl": [2, 3], "cubrid": 6, "cubridadapt": 6, "cubriddb": 6, "current": [2, 5, 6, 8, 10, 13, 14, 15, 16], "current_record": 6, "cursor": [6, 16], "curt": 6, "custom": [0, 2, 4, 8, 13, 15, 16, 17], "custom_check": 12, "custom_qualifi": 6, "customiz": [0, 12, 14], "cx_oracl": 6, "cyclic": 6, "czech": 11, "d": [2, 7, 10, 12], "daemon": [2, 16], "dai": 12, "dal": [2, 4, 7, 12, 13, 14, 16, 17], "dan": 0, "dancer": 13, "danger": 15, "dash": 12, "dashboard": [0, 2, 4, 5, 8, 12, 16, 17], "dashboard_mod": 2, "data": [0, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16, 17], "data_label": 16, "databas": [0, 1, 3, 4, 7, 13, 14, 15, 16, 17], "datalist": 16, "date": [2, 6, 14], "datetim": [4, 5, 6, 7, 12, 16], "datetimewidget": 12, "daunt": 1, "db": [2, 3, 4, 5, 7, 10, 12, 13, 14, 15, 16], "db1": 6, "db2": 6, "db2adapt": 6, "db2ibm": 6, "db2pyodbc": 6, "db_a": 6, "db_b": 6, "db_codec": 6, "db_folder": [5, 7, 14], "db_name": 6, "db_uid": 6, "dbadmin": 4, "dbio": 12, "dbo": 6, "dbset": 12, "dbstore": 5, "dc": 13, "dd": 12, "de": [0, 1, 6, 11], "deal": [1, 6, 16], "dealfaro": 0, "debounc": 16, "debug": [2, 4, 5, 6, 8], "debugg": [1, 15], "decid": [6, 12, 13, 14], "decim": [4, 6, 12], "declar": [0, 4, 5, 6], "decod": 6, "decode_credenti": 6, "decor": [1, 4, 6, 15, 17], "decres": 13, "dedic": [1, 4, 5, 10, 15], "def": [4, 5, 6, 7, 10, 12, 13, 14, 15, 16], "default": [0, 2, 3, 4, 5, 7, 10, 12, 13, 14, 15, 16], "defer": [6, 16], "defin": [2, 4, 5, 8, 12, 13, 14, 15, 16], "define_t": [5, 7, 12, 13, 14, 15], "definit": [6, 7, 12, 14, 15], "degre": 6, "del": [6, 10], "delai": [6, 16], "deleg": 6, "delet": [2, 4, 7, 12, 14, 16], "delete_record": 6, "delimit": [0, 5, 6, 8, 15], "deliv": 16, "delta": 16, "demand": 6, "demo": [1, 2, 16], "deni": 7, "denorm": [6, 7], "depend": [0, 2, 3, 4, 5, 6, 8, 12, 13, 14, 15], "deploi": 2, "deploy": 1, "deployment_tool": 2, "deprec": [6, 12, 16], "deriv": [6, 12], "desc": 14, "descend": 10, "describ": [2, 3, 4, 5, 6, 12], "descript": [6, 7, 12, 13, 14], "design": [0, 4, 6, 8, 13, 16, 17], "desir": [5, 8, 12, 13], "dest": [4, 6], "detail": [0, 1, 2, 5, 6, 7, 8, 10, 12, 14, 16], "detail_field": 12, "determin": [4, 5, 6, 8, 11, 12, 13, 14], "determinist": 5, "dev": 13, "develop": [0, 1, 4, 5, 6, 13, 14, 15, 16], "development_tool": 2, "devic": 8, "di": [0, 5], "diagram": 7, "dialect": 6, "dict": [4, 5, 6, 11, 12, 14, 15, 16], "dictionari": [4, 5, 10, 11, 15], "did": [0, 4, 6, 16], "differ": [0, 2, 4, 5, 6, 8, 11, 12, 13, 14, 15, 16], "difficult": [2, 16], "digit": [4, 6, 12, 13], "dimens": 12, "dir": [2, 12, 16], "direct": [2, 4, 5, 6, 8, 12], "directli": [1, 2, 4, 6, 7, 8, 10, 12, 13, 14, 16], "directori": 13, "dirnam": [5, 7, 14], "disabl": [6, 7, 10, 12, 14], "disallow": [6, 12], "discard": 6, "discord_client_id": 13, "discord_client_secret": 13, "discount": 6, "discounted_tot": 6, "discounted_total_pric": 6, "discounted_unit_pric": 6, "discov": [11, 13], "discoveri": 2, "discrimin": 13, "discuss": [1, 3, 6, 8, 10, 12], "disk": 5, "dismiss": [5, 15], "displai": [3, 4, 5, 6, 8, 10, 12, 13, 14, 16], "distinct": [8, 12], "distributed_transaction_commit": 6, "ditch": 0, "div": [4, 5, 8, 12, 14, 15, 16], "divis": [8, 10, 12], "divmod": 6, "django": [0, 1, 6, 15], "do": [1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "do_connect": 6, "doc": [1, 16], "dockerfil": 2, "doctor": 6, "doctyp": [8, 14], "document": [3, 5, 6, 8, 10, 12, 16], "doe": [0, 2, 5, 6, 8, 12, 13, 14, 15, 16], "doesn": [5, 6, 14], "dog": [11, 12, 16], "doh": 12, "dom": [8, 17], "domain": [12, 13, 17], "don": [1, 2, 3, 4, 6, 8, 12, 13, 14], "done": [2, 4, 5, 6, 8, 12, 14, 15], "dot": 12, "doubl": [2, 6, 8], "doubt": 14, "down": [6, 12, 16], "downfal": 14, "download": [2, 12], "download_url": 12, "downsid": [6, 15], "dramat": 6, "driven": 0, "driver": 6, "driver_arg": 6, "drop": 12, "dropdown": [12, 14, 16], "dsn": 6, "due": [6, 7, 12], "dummi": [6, 8], "dummyrespons": 8, "dump": [5, 6, 16], "dumpfil": 6, "duplic": 6, "durabl": 7, "dure": [6, 14], "dynam": [6, 7, 8, 10, 12, 13, 17], "e": [2, 4, 5, 6, 7, 8, 10, 12, 13, 16], "each": [2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "earli": [5, 6], "earlier": [13, 16], "easi": [0, 5, 6, 8, 10, 12], "easier": [0, 6, 7, 8], "easiest": [6, 13], "easili": [4, 5, 6, 8, 10, 12, 14], "ebook": 1, "echo": 4, "ecosystem": 16, "edg": 16, "edit": [0, 2, 3, 4, 8, 10, 12, 13, 14, 15, 16], "edit_sidecar": 16, "editor": [1, 8], "educ": 2, "effect": [3, 5, 6, 12], "effici": [0, 1, 5, 6, 15], "effort": 1, "efg": 10, "either": [6, 10, 12], "el": 10, "element": [6, 8, 10, 12, 14, 16], "els": [2, 5, 6, 12, 13, 14, 15, 16], "elt": 16, "emac": 8, "email": [2, 5, 8, 12, 13, 15, 16], "emails_onvalid": 12, "emb": 10, "embed": [5, 8, 12], "ember": 12, "emerg": 16, "emphas": 10, "employe": [5, 14], "empti": [2, 4, 6, 12, 16], "empty_regex": 12, "en": [5, 7, 16], "enabl": [1, 5, 6, 7, 10, 13, 15, 16], "enable_record_vers": 6, "encapsul": [8, 10], "enclos": [6, 8], "encod": [5, 6, 12, 16], "encrypt": [0, 2, 5], "end": [4, 6, 8, 10, 12, 13, 14, 15, 16], "endpoint": [13, 16], "enforc": [5, 6, 12, 15], "engin": [6, 12], "english": 5, "enhanc": 6, "enough": [12, 13, 14], "enqueu": 16, "enqueue_run": 16, "ensur": [4, 6, 12], "enter": [4, 5, 10, 12, 13], "enterpris": [1, 13], "entir": [4, 6, 8, 15, 16], "entiti": 6, "entity_quot": 6, "entri": [0, 6, 7, 11, 12, 14, 15], "entropi": 12, "env": [1, 15], "envelop": 16, "environ": [0, 1, 4, 5, 6, 15], "epub": 1, "eq": 7, "equal": [6, 7, 10, 13, 16], "equip": 5, "equival": [4, 5, 6, 10, 12, 15], "errlog": 5, "error": [1, 2, 3, 5, 6, 7, 8, 10, 12, 15, 16], "error_messag": 12, "errorlog": 2, "escap": [6, 8, 10], "especi": [0, 2, 4, 5, 6, 12, 13], "esprima": 4, "establish": [6, 15], "etc": [4, 5, 6, 7, 11, 15], "eval": 16, "evalu": [5, 6, 7, 8, 12, 16], "even": [1, 4, 5, 6, 8, 10, 12, 14, 15, 16], "event": [2, 6, 16], "event_tim": 6, "eventu": 5, "ever": 6, "everi": [0, 2, 4, 5, 6, 13, 14, 15, 16], "everyon": [0, 1], "everyth": [2, 15, 16], "evolut": 0, "ex": 2, "exact": 15, "exactli": [5, 6, 8], "exampl": [1, 2, 3, 4, 5, 6, 8, 10, 11, 13, 17], "excel": [1, 14], "except": [2, 4, 5, 6, 10, 12, 13, 16], "excerpt": 8, "exclud": 12, "exclus": [0, 5, 6, 12, 13], "execut": [1, 2, 5, 6, 8, 10, 12, 15, 16], "exercis": [5, 13], "exist": [2, 5, 6, 12, 13, 14, 16], "exit": [2, 5], "exp": [4, 6], "expand": [1, 3], "expect": [2, 4, 5, 6, 10, 12, 15], "experi": [1, 12, 13], "experienc": 0, "experiment": [2, 5], "expir": [5, 6], "explain": [4, 5, 6, 12, 14], "explanatori": 6, "explicit": [0, 2, 4, 5, 6, 12, 13], "explicitli": [0, 2, 5, 6, 8, 10, 12, 16], "explict": 6, "explor": 3, "export": 17, "export_to_csv_fil": 6, "expos": [0, 3, 4, 5, 6, 7, 13], "express": [4, 7, 8, 10, 11, 12, 14, 16], "extend": [0, 5, 6, 12, 13, 15, 16], "extens": [3, 6, 12, 15], "extern": [2, 4, 6, 13], "extra": [5, 6, 12, 13], "extra_field": 5, "extract": [6, 12, 16], "f": [6, 13, 14, 15, 16], "fa": 14, "face": [1, 6], "facebook": [0, 5], "facilit": 4, "fact": [4, 5, 6, 15], "factori": [5, 6, 10], "fail": [3, 6, 8, 12, 13, 16], "failur": [4, 12, 16], "fake": 6, "fake_migrate_al": 6, "fall": 12, "fals": [2, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "famou": [1, 6], "fanci": 16, "far": [6, 16], "fast": [0, 4, 16], "faster": [0, 6, 8, 15], "father": 6, "father_id": 6, "favorite_color": 5, "fb00": 12, "fdb": 6, "fe80": 12, "feasibl": [2, 12], "featur": [0, 2, 4, 7, 8, 15, 17], "februari": 5, "feed": 6, "fetch": [15, 16], "fetchon": 6, "few": [6, 8, 13, 16], "ff00": 12, "fid": 6, "field": [2, 4, 5, 7, 10, 13, 15, 16, 17], "field1": 6, "field2": 6, "field3": 6, "field_id": 14, "fieldnam": [6, 16], "fieldstorag": 12, "fifth": 14, "file": [0, 1, 2, 3, 5, 6, 8, 10, 13, 14, 16, 17], "file_cont": [6, 16], "file_nam": [6, 16], "file_path": 15, "filenam": [2, 4, 5, 6, 12], "filep": 4, "filepath": 4, "filesystem": [0, 4, 5, 6], "fileuploadwidget": 12, "fill": [12, 16], "filter": [2, 4, 7, 12, 13], "filter_in": 12, "filter_out": 14, "final": [1, 2, 4, 6, 11, 13, 14], "find": [1, 2, 4, 8, 11, 12, 13, 14, 16], "find_by_tag": 13, "find_match": 11, "findal": 12, "fine": [2, 13, 14], "fire": 6, "firebird": 6, "firebird_embed": 6, "firebirdadapt": 6, "firebirdembed": 6, "firebirdembeddedadapt": 6, "firefox": [3, 16], "firfox": 16, "first": [0, 3, 5, 7, 8, 10, 12, 13, 14, 15, 16, 17], "first_nam": [4, 5, 13, 14], "first_onli": 10, "first_row": 6, "first_row_dict": 6, "fist": 12, "fit": 12, "five": 6, "fix": [5, 15], "fixtur": [0, 2, 4, 6, 10, 13, 15, 17], "fk_field": 16, "fk_tabl": 16, "fkdaog": 8, "flag": 12, "flash": [8, 12, 16, 17], "flask": [0, 15], "flexibl": [0, 6, 13, 14, 16], "flight": 7, "float": [4, 12], "flow": 13, "fn": 2, "focu": 16, "fold": 6, "folder": [1, 2, 3, 4, 5, 7, 11, 12, 14, 15], "follow": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "font": [8, 12, 14], "foo": 14, "foot": 13, "footer": [8, 10], "forbid": 12, "forbidden": 12, "forc": [2, 5, 6, 12, 13], "foreground": 12, "foreign": [1, 14], "foreign_key_check": 6, "forgeri": 5, "forget": [6, 14], "form": [0, 4, 5, 6, 7, 11, 13, 14, 17], "form_bas": 12, "form_custom_widget": 12, "form_exampl": 12, "form_minim": 12, "form_nam": 12, "form_widget": 12, "format": [4, 5, 7, 10, 11, 13, 14], "formatt": 16, "formdata": 7, "former": 6, "formstyl": [12, 14, 16], "formstylebootstrap4": 12, "formstylebulma": [12, 14, 16], "formstyledefault": [12, 14], "formstylefactori": 16, "forum": 3, "forward": [1, 3], "found": [2, 5, 6, 12], "four": [2, 6], "fourth": 12, "fp": 5, "fr": 11, "framework": [0, 1, 2, 4, 5, 6, 12, 13, 14, 15, 16], "free": [1, 5, 6, 13], "freetext": 16, "frequent": [1, 6, 12], "friendli": [0, 5, 8], "from": [0, 1, 3, 5, 7, 8, 10, 11, 12, 13, 14, 16, 17], "from_add": 16, "from_addr": 16, "from_address": 13, "from_email": 16, "front": [0, 4, 8, 12, 16], "frontend": 16, "fsstorag": 5, "ftp": 12, "full": [2, 4, 6, 8, 12, 14, 16], "fulli": [1, 4, 5, 6, 12], "fullnam": 6, "func": [2, 5], "function": [0, 2, 4, 5, 6, 7, 10, 13, 14, 15, 16], "functool": 16, "furnitur": 5, "further": 13, "futur": [5, 15, 16], "g": [1, 2, 6, 10, 12], "gain": 2, "gather": 14, "gavgavian": 1, "gender": 6, "gener": [0, 1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16], "generate_time_based_cod": 13, "german": 11, "get": [1, 2, 4, 5, 6, 8, 12, 13, 14, 15, 16, 17], "get_cooki": 16, "get_us": [4, 5, 13, 15], "get_var": [7, 15], "getvalu": 6, "gevent": [1, 2], "geventw": 2, "geventwebsocketserv": 2, "gia": 5, "gib": 6, "gif": 12, "git": [1, 2], "github": [2, 3, 4, 5, 6, 14], "gitlat": 1, "give": [0, 2, 4, 5, 6, 8, 13, 14], "given": [2, 5, 6, 8, 12, 16], "global": [0, 5, 6, 8, 13, 15, 16], "go": [1, 2, 4, 6, 8, 14, 16], "goal": 0, "goe": [6, 12], "good": [2, 3, 12], "googl": [0, 3, 5, 8, 12, 14], "googledatastor": 6, "googledatastoreadapt": 6, "googlemysql": 6, "googlepostgr": 6, "googlesql": 6, "googlesqladapt": 6, "gotcha": 17, "gotta": 8, "granular": 6, "granulari": 12, "graphic": 6, "graphql": 7, "great": 16, "greater": 7, "greedi": 4, "green": [4, 6, 12, 15], "grid": [0, 4, 17], "grid_class_styl": 14, "grid_tutori": 14, "gridactionbutton": 14, "gridclassstyl": 14, "gridclassstylebulma": 14, "group": [0, 2, 3, 5, 13, 15], "group_nam": 13, "groupbi": 12, "grow": [0, 6, 16], "gt": [7, 10], "guarante": [5, 6, 8, 15, 16], "guid": 2, "guidelin": 13, "gunicorn": 2, "gunicorngev": 2, "gz": 12, "h": [2, 12], "h1": [4, 8], "h2": [8, 12], "ha": [0, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "habit": 2, "had": 6, "hamburg": 8, "hand": [2, 6, 16], "handi": 6, "handl": [2, 4, 5, 6, 12, 14, 15, 16], "handler": 4, "hang": 1, "happen": [5, 6], "hard": 15, "hardcod": 5, "has_membership": 13, "hash": [2, 6, 12], "have": [0, 1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16], "he": 6, "head": [4, 8, 12, 14], "header": [4, 5, 6, 8, 10, 14, 16], "height": [12, 14, 16], "hello": [4, 5, 6, 8, 10, 12, 13, 16], "help": [2, 6, 7, 8, 10, 12, 15, 17], "helper": [0, 4, 5, 6, 8, 12, 14, 15, 16, 17], "henc": [0, 2, 4, 5, 6, 12, 13], "her": 12, "here": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16], "hesit": 6, "hex": 12, "hh": 12, "hi": [6, 12, 16], "hidden": [6, 12, 16], "hidden_div": 16, "hidden_input": 16, "hide": [6, 14], "hierarch": [6, 13], "high": [12, 13], "higher": 6, "highest": 12, "highli": [1, 4, 14], "highlight": [1, 8], "hint": [14, 17], "histor": [0, 6], "histori": 12, "hit": [2, 6], "hmac": 12, "ho": 5, "hold": [6, 16], "home": 8, "host": [1, 2, 4, 5, 12, 13], "hour": 12, "hous": 15, "housekeep": 6, "how": [0, 2, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16, 17], "howev": [6, 8, 10, 12, 13, 14, 16], "href": [8, 10, 13, 14], "hs256": 5, "html": [1, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16], "html5": 8, "htmx": [14, 17], "htmx_form": 16, "htmx_form_demo": 16, "htmx_grid": 16, "htmx_list": 16, "htmxautocompletewidget": 16, "http": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "httrespons": 5, "hulk": 12, "hundr": 16, "hx": 16, "hypertext": 16, "hyphen": 10, "i": [1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17], "i18n": [5, 11], "ibm_db_dbi": 6, "icon": [8, 14], "id": [0, 1, 2, 3, 7, 8, 10, 12, 13, 14, 15, 16], "id1": 6, "id2": 6, "id_field_nam": 14, "id_valu": 14, "idea": 6, "ident": [6, 7, 15], "identifi": [4, 5, 6, 10, 12], "idn": 12, "ie": 12, "ietf": 12, "ifram": 12, "ignor": [4, 5, 6, 8, 10, 12, 14, 16], "ignore_attribute_plugin": 14, "ignore_common_filt": [6, 12], "ignore_field_cas": 6, "iip": 6, "illustr": [6, 8], "imag": [6, 8, 10, 12], "image_fil": 6, "imagin": [5, 6, 8, 16], "imap": 6, "imapadapt": 6, "imaplib": 6, "img": [4, 12], "immedi": [2, 6, 14], "immens": 0, "implement": [4, 5, 6, 8, 12, 13, 15, 16], "impli": 7, "implic": 5, "implicit": 6, "implicitli": [6, 12], "import": [0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17], "import_and_sync": 6, "import_from_csv_fil": 6, "impos": 8, "impot": 12, "improv": [12, 13, 14], "includ": [0, 4, 5, 6, 7, 10, 12, 14, 15, 16], "include_action_button_text": 14, "include_path": 4, "inclus": [0, 12], "incom": 2, "incorrect": 8, "increas": 5, "increment": 6, "inde": [0, 5], "indent": 8, "independ": [0, 5, 6, 13], "index": [1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16, 17], "indic": [4, 6, 12, 13, 15, 16], "individu": [0, 2, 5, 6, 7, 8, 12], "ineffici": [5, 6], "infinit": 6, "info": [2, 4, 5, 6, 13], "inform": [1, 5, 6, 7, 12, 13, 15, 17], "informix": 6, "informixadapt": 6, "informixdb": 6, "informixs": 6, "ingr": 6, "ingredi": 4, "ingresadapt": 6, "ingresdbi": 6, "ingresu": 6, "ingresunicod": 6, "ingresunicodeadapt": 6, "init": 14, "initi": [0, 2, 4, 5, 8, 12], "inject": [6, 8, 12, 14, 17], "inlin": [10, 16], "inner": [5, 8, 16], "input": [2, 4, 5, 6, 8, 12, 13, 14, 16], "insensit": [6, 12], "insert": [3, 5, 7, 8, 12, 13, 14, 16], "insid": [1, 2, 4, 5, 6, 8, 10, 12, 14, 15, 16], "inspector": 8, "inspir": 7, "instal": [0, 1, 3, 4, 5, 6, 13, 16, 17], "instanc": [4, 5, 6, 10, 12, 13, 14, 16], "instanti": [6, 13, 14, 15], "instead": [1, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "instruct": [2, 12], "int": [4, 6, 12, 13, 15], "int2ip": 6, "integ": [2, 4, 6, 7, 10, 12, 16], "integr": [0, 1, 8, 16], "intend": [6, 16], "intention": 12, "interact": [0, 5], "interchang": 13, "interfac": [0, 3, 5, 6, 14, 15, 16], "intermedi": 6, "intern": [0, 1, 4, 5, 6, 8, 13, 14], "internation": [0, 4, 5, 12, 15, 17], "interpret": [0, 6, 7, 10], "intersect": 6, "introduc": [0, 8], "introduct": [1, 2, 17], "intuit": 12, "invalid": [2, 5, 6, 12, 13], "invert": [6, 12], "invis": 6, "involv": [6, 8, 14, 15], "io": [2, 6], "ip": [2, 6, 12], "ip2int": 6, "ip_list": 13, "ipaddr": 6, "ipaddress": 13, "iptabl": 2, "ipv4": [6, 12], "ipv4address": 13, "ipv4network": 13, "ipv6": 12, "is_6to4": 12, "is_act": 6, "is_automat": 12, "is_dat": 6, "is_datetim": 6, "is_decimal_in_rang": 6, "is_empty_or": 6, "is_float_in_rang": 6, "is_imperson": 13, "is_in_db": [6, 14], "is_in_set": 6, "is_int_in_rang": 6, "is_json": 6, "is_length": 6, "is_link_loc": 12, "is_localhost": 12, "is_multicast": 12, "is_not_empti": 6, "is_null_or": 14, "is_priv": 12, "is_publ": 6, "is_reserv": 12, "is_rout": 12, "is_teredo": 12, "is_tim": 6, "isdir": [7, 14], "isn": [12, 14], "iso": 7, "isol": 16, "issu": [0, 3, 4, 6], "ital": 10, "italian": [5, 11], "item": [6, 7, 8, 10, 12], "itemize1": 8, "itemize2": 8, "iter": [8, 12], "iterselect": 6, "its": [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "itself": [2, 6, 12, 16], "iv": 6, "j": [0, 4, 5, 8, 11, 15, 17], "javascript": [1, 4, 7, 10, 16], "jdbc": 6, "jdbcpostgr": 6, "jdbcpostgresqladapt": 6, "jdbcsqlite": 6, "jdbcsqliteadapt": 6, "jetbrain": 1, "jim": [0, 1, 14], "jinja2": 5, "job": [7, 12, 14], "john": [0, 6], "join": [4, 5, 7, 12, 13, 14, 15, 17], "jonathan": 6, "journalist": [7, 14], "journei": 1, "jpeg": 12, "jpg": 12, "jpsteil": 14, "jqueri": [10, 12, 16], "jsl": 16, "json": [0, 1, 2, 4, 5, 6, 7, 11, 12, 15, 16], "jump": 1, "junk": 6, "just": [1, 2, 4, 5, 6, 7, 8, 12, 14, 16], "jwt": 5, "jython": 6, "k": [5, 8, 15], "karg": 10, "kbyte": 5, "keep": [1, 2, 4, 5, 6, 7, 8, 10, 12, 14, 15], "keep_valu": 12, "kei": [2, 4, 5, 7, 10, 11, 12, 13, 16, 17], "keller": 0, "ken": 6, "kent": [7, 14], "kevin": 0, "keycod": 16, "keyup": 16, "keyword": [8, 10], "kfield": 16, "kill": 16, "kind": [0, 4, 13], "kinterbasdb": 6, "know": [1, 2, 4, 5, 6, 8, 14], "knowledg": 1, "known": [6, 8], "known_express": 11, "ktabl": 16, "kwarg": [6, 16], "ky8iq0g4b3cyey6wyhn3yt9pw0xpsrivlkmxe40ptknxrlnz9": 8, "l": [2, 12], "label": [6, 7, 8, 12, 14, 15, 16], "lack": [6, 12, 15], "lambda": [5, 6, 10, 12, 14, 16], "lang": 5, "languag": [0, 1, 4, 5, 11, 15, 16, 17], "larg": [6, 12], "larger": 0, "last": [1, 5, 8, 12, 13, 14, 16], "last_insert_id": 6, "last_nam": [5, 13, 14], "last_row": 6, "lastdot": 12, "lastrowid": 6, "later": [1, 2, 4, 5, 6, 8, 10, 12, 13, 14], "latest": [2, 6, 15], "latin1": 6, "latter": [6, 12, 15], "launch": [1, 2], "launcher": 1, "lax": 5, "layer": [5, 15, 17], "layout": [12, 13, 14, 15, 16, 17], "lazi": [2, 4, 15], "lazili": 16, "lazy_t": 6, "lazy_total_pric": 6, "ldap": [0, 5], "ldap_plugin": 13, "ldap_set": 13, "ldapplugin": 13, "lead": [5, 14], "leader": 16, "learn": [1, 6], "least": [1, 2, 6, 12, 15, 16], "leav": [5, 6, 13], "left": [8, 12, 14, 16], "len": [12, 16], "length": [6, 12], "less": [7, 12, 15, 16], "let": [2, 6, 7, 12, 16], "letter": 12, "level": [2, 4, 6, 7, 11, 12, 13], "leverag": 0, "li": [8, 12, 13, 16], "lib": [2, 8, 10, 14], "libldap2": 13, "librari": [0, 2, 4, 6, 11, 12, 13, 15, 16], "libsasl2": 13, "libsass": 4, "licens": 1, "life": 16, "lifespan": 12, "lifetim": 5, "lighter": [6, 15], "like": [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16], "limit": [0, 2, 5, 6, 7, 8, 12, 13, 15, 16], "line": [4, 5, 6, 7, 8, 10, 12, 13, 14, 16, 17], "link": [2, 6, 7, 8, 10, 12, 13, 14, 16], "lint": 1, "linux": 2, "list": [0, 1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 16], "list_of_field": 6, "listabl": 6, "listen": [2, 3, 4], "listproperti": 6, "liststringproperti": 6, "listwidget": 12, "littl": [1, 5, 6], "live": [6, 16], "ll": [1, 2, 3, 5, 6, 8, 12, 14, 16], "load": [3, 4, 5, 6, 8, 14, 16], "loazkji": 8, "local": [3, 5, 6, 12, 13, 14, 15], "localhost": [2, 4, 5, 6, 12], "locat": [2, 4, 8, 15], "lock": [5, 6, 16], "log": [2, 3, 4, 5, 6, 8, 10, 13, 15, 16], "logerror": 5, "logfil": 6, "logging_level": 2, "logic": [2, 5, 12, 13, 14, 15, 16], "login": [0, 2, 4, 5, 6, 8, 12, 13, 15, 17], "logo": [0, 8, 10], "logout": [0, 8, 13], "long": [0, 2, 12, 16], "longer": [0, 6], "longhash_tablenam": 6, "longtext": 6, "look": [2, 3, 4, 5, 6, 7, 10, 12, 13], "lookup": [5, 7, 12], "loop": [6, 8, 16], "lose": [6, 12], "lost": [1, 5, 6, 15], "lot": [1, 4, 6], "love": 0, "low": 0, "lower": 12, "lowercas": 12, "lowest": 12, "lru": 5, "lt": [2, 10], "luca": [0, 1], "m": [0, 2, 6, 10, 12], "mac": 6, "machin": [2, 6], "macneiln": 2, "maco": 2, "made": [1, 2, 6, 8, 12, 14], "magic": 0, "mai": [2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "mail": [1, 3, 6, 12, 16], "mailto": 12, "main": [0, 1, 2, 4, 6, 8, 14, 15, 16, 17], "maintain": [6, 8, 14, 16], "mainten": 16, "major": 6, "make": [0, 1, 2, 5, 8, 10, 11, 12, 13, 14, 15, 16], "makefil": 2, "man": 16, "manag": [0, 1, 2, 3, 4, 5, 6, 12, 13, 14, 16], "mandatori": [5, 6, 8], "mani": [0, 1, 2, 4, 5, 7, 8, 12, 14, 15, 16], "manipul": [4, 6], "manner": [5, 15], "manual": [2, 3, 4, 6, 10, 13, 14], "map": [2, 6, 10, 11, 12, 15, 17], "map_non": 6, "marco": 6, "margin": [8, 16], "mark": [6, 13], "massimo": [0, 6], "master": [1, 2, 6, 14], "match": [2, 4, 5, 6, 7, 8, 10, 11, 12, 16], "materi": 6, "math": 16, "mathemat": 6, "matter": 15, "max": [11, 12, 16], "max_concurrent_run": 16, "maximum": [5, 6, 12], "maxip": 12, "maxlen": 12, "maxsiz": 12, "mayb": [2, 5], "md5": 12, "me": [10, 12, 14, 16], "mean": [1, 2, 3, 5, 6, 8, 12, 13, 15], "mechan": [0, 4, 5, 6, 8, 10, 13, 15, 16], "meet": 6, "member": [12, 13], "membership": [0, 5, 12, 13, 15], "memcach": [0, 6], "memoiz": 17, "memori": 5, "mention": [5, 13, 16], "menu": [8, 12, 13], "merg": 6, "mess": 1, "messag": [2, 4, 5, 7, 8, 12, 14, 15, 17], "met": 6, "meta": [8, 10], "metadata": 6, "metatag": 10, "method": [4, 5, 7, 8, 10, 12, 13, 14, 16, 17], "mfa": 13, "micah": 0, "microsoft": [1, 13], "mid": 6, "middlewar": 5, "might": [4, 7, 12], "migrat": [15, 17], "migrate_en": 6, "mileston": 1, "min": [8, 12, 14], "min_length": 12, "mind": [1, 4, 6, 7, 8, 12], "minim": [4, 5, 14, 17], "minimalist": [8, 15], "minimalist_pag": 8, "minimum": [6, 12], "minip": 12, "minor": [8, 12, 15], "minsiz": 12, "minut": 12, "miss": [2, 13, 14], "mistak": 2, "mix": 8, "mkdir": [2, 4, 7, 14], "mm": 12, "mode": [2, 4, 5, 12, 13], "model": [0, 3, 4, 7, 10, 12, 14, 15, 16], "modern": [6, 13, 16], "modif": [2, 12], "modifi": [2, 4, 6, 7, 8, 12, 14, 15, 16], "modified_bi": 6, "modified_on": 6, "modul": [0, 1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 17], "modular": [0, 6, 8, 15], "moment": 6, "mongo": 6, "mongodb": 6, "mongodbadapt": 6, "monkei": 5, "monolith": 0, "monospac": 10, "month": 12, "more": [0, 1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16], "moreov": [6, 16], "most": [0, 3, 4, 5, 6, 12, 13, 14, 15, 16], "mostli": 5, "mother": 6, "mother_id": 6, "mount": 13, "move": 6, "mssql1": 6, "mssql1n": 6, "mssql2": 6, "mssql2adapt": 6, "mssql3": 6, "mssql3adapt": 6, "mssql3n": 6, "mssql4": 6, "mssql4adapt": 6, "mssql4n": 6, "mssqladapt": 6, "mssqln": 6, "mtabl": 0, "much": [0, 1, 4, 5, 6, 8, 12, 14, 15, 16], "multi": [1, 2, 6, 15], "multicast": 12, "multipl": [0, 1, 2, 4, 6, 8, 10, 11, 12, 15, 16, 17], "multiprocess": 5, "multiselect": 12, "multius": 13, "must": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "my": [5, 8, 10, 12, 13, 16], "my_app": 4, "my_id": 16, "my_password_fil": 2, "my_task": 16, "my_url_path": 15, "my_var": [5, 10], "myapp": [2, 4], "myclass": 10, "mycompon": 16, "mycustomwidget": 12, "mydb": 6, "myerror": 5, "myfield": 6, "myfil": 6, "myfixtur": 5, "myfunct": 2, "myidx": 6, "myobj": 6, "myobjnam": 6, "myorder": 6, "myqueri": 6, "myrecord": 6, "mysaltvalu": 12, "mysendgridsend": 16, "myset": 6, "mysidebar": 8, "mysqladapt": 6, "mysqldb": 6, "mysqldv": 6, "mystyl": 12, "mytabl": 6, "myvalu": 6, "myvirtualfield": 6, "myvirtualfields1": 6, "myvirtualfields2": 6, "n": [5, 6, 11, 16], "name": [2, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16], "nameonli": 6, "narrow": 6, "nativ": 6, "native_json": 12, "nav": 8, "navbar": [8, 13], "navig": [8, 14, 16], "ndb": 6, "ne6fz": 8, "necessari": [6, 8, 10], "need": [0, 1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "neg": [6, 12], "negat": 6, "neither": [6, 12], "nest": [6, 8, 10], "nested_select": 6, "network": [6, 12, 13], "never": [5, 6, 8, 12, 15, 16], "nevertheless": 6, "new": [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16], "new_app": [8, 10], "new_password": 16, "new_sidecar": 16, "newer": 8, "newli": [4, 6, 11], "newlin": [6, 12], "next": [1, 2, 5, 6, 8, 12, 16], "nginx": [2, 4], "nice": 10, "nicer": 16, "nico": 0, "nid": 6, "no_backslash_escap": 6, "no_tabl": [12, 16], "node": [4, 16], "non": [4, 5, 6, 12, 14, 15], "none": [2, 5, 6, 7, 10, 12, 13, 14, 15, 16], "nor": [2, 6, 12], "normal": [2, 4, 5, 6, 8, 10, 12, 14, 16], "northwind": 1, "nosqladapt": 6, "not_author": 13, "notat": [6, 10], "note": [1, 2, 4, 5, 8, 10, 12, 15, 16], "noth": [2, 5, 6, 12, 16], "notic": [2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "notnul": 6, "notset": 2, "now": [0, 2, 4, 5, 6, 8, 12, 14, 16], "nowadai": 1, "null": [6, 7, 12, 16], "num": 6, "number": [2, 4, 5, 6, 8, 11, 12, 13, 14, 16], "number_work": 2, "numer": [6, 12], "o": [4, 5, 7, 12, 14], "oauth": 13, "oauth2": [0, 5], "oauth2discord": 13, "oauth2facebook": 13, "oauth2googl": 13, "obj": [6, 8], "object": [0, 5, 6, 7, 8, 10, 11, 12, 15, 17], "observ": 6, "obtain": [2, 4, 6, 13, 16], "obviou": [6, 7, 8, 13], "obvious": [6, 12], "occasion": [6, 12], "occur": [2, 6, 8, 12, 16], "odd": [6, 8], "off": [0, 2, 4, 6, 14, 16], "offici": [0, 6, 12, 16], "offset": [6, 7], "often": [4, 6, 13, 16], "ok": [10, 15], "old": [2, 5, 12, 15], "older": 12, "ombott": [0, 4, 15], "omit": 2, "on_delete_act": 6, "on_error": 5, "on_fals": 5, "on_request": [5, 13], "on_success": 5, "onc": [1, 2, 5, 10, 12, 13, 14, 16], "onclick": 16, "ondelet": 6, "one": [0, 2, 3, 4, 5, 7, 8, 11, 12, 13, 14, 16], "ones": [2, 5, 6, 7, 12, 15], "onion": 5, "onkeydown": 16, "onli": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "onlin": [1, 6], "onload": 16, "onvalid": 12, "onward": 6, "opac": 16, "open": [1, 2, 3, 4, 5, 6, 10], "oper": [0, 3, 5, 11, 12, 13, 17], "operationalerror": 12, "oppos": [5, 6, 10], "opposit": [5, 6], "oprow": 6, "optim": [4, 6], "optimis": 6, "option": [0, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17], "oracleadapt": 6, "order": [1, 2, 4, 5, 6, 7, 10, 12, 14, 15], "order_item": 6, "orderbi": [12, 13, 14, 16], "ordereddict": 6, "org": [7, 10, 16], "organ": 4, "origin": [6, 8, 10, 11, 12], "orm": 6, "other": [0, 1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 15, 16, 17], "other_pag": [12, 16], "otherfield": 6, "othert": 6, "otherwis": [4, 5, 6, 8, 12, 13, 14], "oufil": 6, "our": [0, 1, 2, 4, 5, 6, 10, 12, 16], "out": [0, 1, 2, 6, 8, 16], "outer": [5, 12, 16], "outlin": [4, 5], "output": [1, 2, 4, 5, 6, 7, 8, 10, 12, 16], "output_styl": 4, "outsid": [0, 5, 6, 12, 14, 15, 16], "ov": 6, "over": [6, 8, 14, 16], "overcom": 6, "overhead": 6, "overkil": 13, "overload": 6, "overrid": [4, 6, 8, 10, 13, 14, 15, 16], "override_class": 14, "override_styl": 14, "overview": 17, "overwritten": [5, 15], "own": [4, 5, 6, 8, 12, 13, 14, 15, 16], "owner": [6, 12], "owner_id": 6, "owner_id1": 6, "owner_id2": 6, "ownership": 6, "p": [2, 8, 12], "p10n": 11, "p11n": 5, "packag": [0, 6, 10], "pad": [5, 8, 15, 16], "page": [1, 5, 6, 10, 12, 13, 14, 15, 16, 17], "page_head": 8, "page_left_menu": 8, "page_script": 8, "pagin": [6, 14], "pai": 4, "paint": [4, 12], "pair": [6, 10], "pam": [0, 5], "pam_plugin": 13, "pamplugin": 13, "paragraph": [1, 5, 10, 12, 14], "param": [12, 13, 14, 15, 16], "paramet": [1, 2, 4, 5, 13, 16], "parent": [2, 6, 8, 16], "parenthes": 6, "park": [7, 14], "pars": [4, 5, 6, 11, 12, 13], "parsemodul": 4, "parser": 6, "part": [6, 10, 12, 15], "parti": [0, 5], "partial": [4, 6, 15], "particip": 1, "particular": [0, 1, 6, 12, 14, 15, 16], "particularli": 6, "pass": [2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "passphras": 5, "password": [0, 2, 3, 5, 6, 8, 12, 13, 15, 16], "password_fil": 2, "passwordwidget": 12, "patch": 5, "path": [2, 4, 5, 6, 7, 11, 12, 14, 15, 16], "path_to": 2, "pattern": [4, 7], "paus": 2, "payment": 6, "payrol": 5, "pbkdf2": 12, "pc": 3, "pdf": [1, 12], "pdkdf2": 2, "per": [0, 4, 5, 7, 14], "percent": 6, "percentag": 6, "perfect": 0, "perfectli": 14, "perform": [0, 3, 5, 6, 12, 13, 15], "perhap": 5, "period": 16, "permiss": [0, 5, 6, 15], "permit": 6, "permitted_tag": 10, "persist": [5, 6, 13], "person": [2, 6, 7, 12, 14], "persons_and_th": 6, "perspect": 0, "pet": 6, "peter": [7, 14], "phase": 6, "philip": 6, "phone": [12, 16], "photograph": [7, 14], "phrase": 6, "physic": 13, "pick": [4, 5, 13], "piec": [0, 5, 6, 8, 12], "pierro": 0, "pip": [0, 1, 6], "pirsch": 0, "piu": 5, "pixel": 12, "place": [1, 5, 6, 8, 12, 14, 16], "placehold": [6, 11, 12, 16], "plai": [0, 16], "plain": [6, 16], "plan": [1, 16], "platform": [0, 1, 17], "pleas": [5, 6], "plu": [6, 8, 13, 14], "plugin": [4, 5, 12, 14, 16], "plural": [0, 4, 5, 14, 15, 16, 17], "pm": 12, "png": [10, 12], "point": [2, 3, 4, 5, 6, 7, 8, 12, 14, 15], "pointer": 16, "pointless": 6, "polici": [0, 17], "pollut": 5, "pool": [4, 5], "pool_connect": 6, "pool_siz": [5, 6], "poor": 16, "pop": 16, "popul": 6, "popular": [0, 6], "port": [0, 2, 3, 5, 6, 15], "portabl": 6, "portion": 14, "posit": [5, 10], "possibl": [4, 5, 6, 8, 12, 13, 14, 15, 16], "possibli": 6, "post": [4, 6, 7, 10, 12, 13, 14, 15, 16], "post_action_button": 14, "post_text": 6, "post_var": [7, 15], "post_writ": 7, "postel": 11, "postel\u00ed": 11, "postfix": 15, "postgr": 6, "postgreboolean": 6, "postgrenew": 6, "postgrepsyco": 6, "postgrepsycoboolean": 6, "postgrepsyconew": 6, "postgres2": 6, "postgres3": 6, "postgres_nonreserv": 6, "postgresql": [2, 6], "postgresqladapt": 6, "postprocess": 15, "potenti": 2, "power": [2, 6, 7, 13, 15, 16], "pprint": 6, "pr": 1, "practic": [0, 6, 8, 12, 15, 17], "pre": [2, 4, 5, 6, 14], "pre_action_button": 14, "precaut": 2, "preced": [5, 6, 7, 8, 12], "preciou": 14, "predefin": [8, 12, 16], "predetermin": 15, "prefer": [2, 5, 6, 10, 16], "prefix": [2, 4, 6, 7, 8, 12, 15], "preliminari": 1, "prepend": [4, 7, 12, 15, 16], "prepend_schem": 12, "preprocess": 15, "prerequisit": [5, 17], "presenc": [5, 6], "present": [2, 6, 8, 12, 13], "preserv": [0, 5], "press": [3, 4, 14], "pretti": [4, 6, 15], "prevent": [2, 5, 6, 8, 10, 12, 16], "previou": [2, 5, 6, 8, 10, 12, 16], "previous": [5, 6, 12], "price": 6, "prima": 5, "primari": [8, 13], "primarili": 14, "prime": 12, "print": [4, 6, 10, 11, 13, 14, 15, 16], "privat": [5, 12], "probabl": [4, 12], "problem": [0, 1, 6, 8, 13, 14, 16], "procedur": 17, "process": [2, 4, 5, 6, 8, 12, 13, 14, 15, 16], "produc": [0, 2, 5, 6, 8, 10, 12, 13], "product": [2, 4, 6, 16], "product_record": 16, "profil": [0, 8, 13, 15], "program": [1, 2, 3, 6, 8, 10, 12, 16], "programmat": [6, 10], "project": [0, 2, 4, 5, 6], "project_nam": 2, "prompt": [2, 4, 6], "proper": [5, 6, 14], "properli": 4, "properti": [6, 12], "protocol": 3, "prototyp": 16, "provid": [0, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "proxi": 4, "proxy_http_vers": 4, "proxy_pass": 4, "proxy_set_head": 4, "prudent": 6, "pseudo": 6, "psycopg2": 6, "public": 6, "publish": 4, "pull": [1, 6], "punycod": 12, "pure": 6, "purpos": [0, 5, 6, 10, 12, 13, 15], "put": [4, 5, 7, 12, 16], "put_writ": 7, "pwd": 6, "py": [1, 2, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16], "py4web": [2, 3, 4, 5, 7, 8, 10, 12, 13, 14], "py4web_filesystem": 6, "py4web_wsgi": 2, "pyc": 8, "pydal": [0, 2, 3, 5, 6, 7, 12, 13, 14, 15, 16], "pyfilesystem": 6, "pymongo": 6, "pymysql": 6, "pyodbc": 6, "pypi": 2, "pypyodbc": 6, "pysqlite2": 6, "pytd": 6, "python": [0, 2, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 16], "python2": [2, 15], "python3": [1, 2], "pyweb": 15, "q": [2, 5, 6, 14], "qualifi": 6, "quantiti": 6, "queri": [4, 5, 7, 10, 12, 13, 14, 15, 16], "query1": 6, "query2": 6, "queryselector": 16, "queryselectoral": 16, "querystr": 14, "question": [1, 2, 6], "quick": [1, 12], "quickli": [2, 12, 14], "quickstart": 16, "quiet": 2, "quirk": 5, "quit": [1, 2, 5, 7, 8, 12, 14], "quot": [2, 10], "quote_minim": 6, "quote_nonnumer": 6, "quotechar": 6, "r": [2, 6, 12], "race": 12, "radio": [10, 12], "radiowidget": 12, "radiu": 16, "rais": [5, 6, 12, 13, 15], "ram": [5, 6], "randint": [8, 13], "random": [6, 8, 12, 13, 16], "rang": [4, 6, 8, 13, 15], "rapid": [0, 16], "rare": 6, "rather": [6, 8, 10, 12, 16], "raw": 17, "rb": 6, "re": [0, 1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "reach": [2, 5], "react": 16, "reactiv": 16, "read": [1, 4, 5, 6, 7, 14, 16], "readabl": [5, 6, 7, 14, 15], "readi": [2, 6], "readm": [1, 12], "readonli": [2, 12, 16], "real": [2, 4, 7, 8, 12], "real_ident": [6, 7], "realiz": 6, "realli": [1, 4, 6], "reap": 16, "reason": [2, 5, 6, 7, 13, 14, 15], "reassembl": 0, "rebuild": 6, "rebuilt": 6, "rec_id": 7, "recal": 8, "receiv": 6, "recent": [3, 5, 6, 12, 14], "recereiv": 16, "recip": 2, "recogn": [4, 6], "recommend": [1, 4, 5, 6, 12, 16], "record": [7, 12, 13, 14, 15, 16], "record_id": [7, 16], "recov": [6, 12], "recreat": 6, "recurr": 6, "recurs": 8, "recycl": 6, "red": [3, 4, 6, 8, 10, 12], "redefin": 5, "redefinit": 6, "redesign": 0, "redi": 0, "redirect": [4, 5, 12, 13, 14, 16], "reduc": [0, 5, 16], "redund": 6, "ref": [10, 12, 13], "refer": [1, 5, 7, 8, 12, 16], "referenc": [6, 7, 10], "referenced_bi": 7, "reflect": [1, 6], "refresh": 14, "regex": [7, 10, 12], "regexlib": 12, "regist": [0, 5, 6, 8, 13, 15, 16], "register_plugin": 13, "register_task": 16, "register_vue_compon": 16, "registr": [4, 12], "registration_stamp": 12, "regular": [0, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16], "reimplement": 16, "reinstal": 2, "reinstat": 6, "reject": [12, 13], "rel": [4, 6, 8, 14, 15], "relat": [5, 17], "relationship": 6, "releas": [0, 2], "relev": 6, "reli": [0, 4, 6], "reliabl": 1, "reload": [2, 3, 4, 5, 12, 16], "remain": [0, 6, 12], "rememb": [3, 5, 6, 12], "remote_addr": [5, 13], "remov": [0, 2, 4, 6, 10, 12, 13], "renam": [1, 7], "render": [5, 7, 8, 10, 12, 14, 15, 16], "renoir": 8, "reopen": 5, "repackag": 0, "repeat": [6, 12], "replac": [0, 2, 6, 8, 10, 12, 14, 16], "replic": [1, 5], "report": [4, 8], "repositori": [1, 2, 3], "repr_row": 6, "repres": [0, 8, 12], "represent": [10, 16], "representational_state_transf": 7, "representing_field": 12, "request": [0, 1, 2, 5, 6, 7, 12, 13, 14, 15, 16], "request_bodi": 16, "request_reset_password": 13, "request_uri": 4, "requir": [0, 1, 2, 4, 5, 6, 7, 12, 13, 14, 15, 16], "requires_": 15, "requires_login": 15, "requires_membership": 13, "rescu": 6, "reserv": 12, "reset": 6, "reset_password": 13, "resourc": [6, 13, 17], "respect": [6, 10, 12, 14], "respons": [4, 5, 6, 8, 14, 15, 16, 17], "rest": [7, 13], "restapi": [0, 3, 17], "restart": [2, 4, 5, 6, 14], "restor": 6, "restrict": [5, 6, 7, 12, 16], "restructuredtext": 1, "result": [1, 6, 7, 8, 12, 13, 14, 16], "resultset": 6, "ret": 6, "retain": 6, "retri": 6, "retriev": [4, 5, 6, 15, 16], "return": [5, 6, 7, 10, 12, 13, 14, 16], "reus": 6, "revers": [4, 5, 6], "revert": 6, "rewrit": 6, "rewritten": 6, "rfc": 12, "rid": 6, "ride": 8, "right": [1, 2, 3, 5, 6, 8], "road": 16, "robust": 16, "rocket": 15, "rocket3": [2, 15], "rocketserv": 2, "role": [2, 5, 10], "roll": [5, 6], "rollback": [4, 16], "root": [6, 8, 13, 14], "roughli": 6, "rout": [0, 2, 3, 5, 14, 15, 16], "rover": 6, "row": [8, 10, 12, 14, 15, 16], "rows1": 6, "rows2": 6, "rows3": 6, "rows_list": 6, "rows_per_pag": 14, "rpc": 6, "rst": 1, "rule": [4, 5, 8, 12, 14], "run": [1, 3, 4, 5, 8, 13, 15, 16, 17], "run_in_transact": 6, "runtim": [2, 6], "s3": 6, "s_": [12, 16], "s_autocomplet": 16, "s_autocomplete_result": 16, "s_down_kei": 16, "s_search": 16, "safari": 16, "safe": [1, 5, 6, 10, 14, 15], "safer": [6, 12, 14], "safeti": [2, 16], "sai": [6, 13], "said": 7, "sake": 6, "salt": 12, "sam": 0, "same": [0, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "same_sit": 5, "saml": 13, "saml2": 0, "sampl": 13, "sandbox": 16, "sane": 4, "sanit": [5, 10, 12, 15], "santa": 1, "sap": 6, "sapdb": 6, "sapdbadapt": 6, "sass": 4, "sass_compil": 4, "save": [1, 2, 4, 5, 6, 11, 12], "scaffold": [2, 4, 5, 6, 8, 12, 15, 16], "scaffold_zip": 2, "scale": [5, 8], "scan": 6, "scare": 1, "schafer": 1, "schedul": 17, "scheduled_for": 16, "schema": 6, "scheme": 12, "school": 13, "scope": 4, "score": 16, "score_input": 16, "scratch": 17, "script": [2, 8, 12, 16], "sdk": 2, "se": [6, 14], "seamlessli": 8, "search": [0, 1, 2, 3, 6, 10, 12, 13, 17], "search_button_text": 14, "search_form": 14, "search_queri": 14, "search_text": 14, "search_valu": 16, "searchabl": 6, "sec": 16, "second": [5, 10, 12, 13, 15], "secret": [5, 13], "section": [2, 4, 6, 8, 12, 13], "secur": [0, 2, 7, 13], "see": [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 16], "seem": 6, "seen": [0, 5, 6, 7, 10, 12, 13, 14, 16], "segment": 4, "select": [3, 4, 5, 7, 11, 12, 13, 14, 15, 16, 17], "selected_el": 16, "selected_id": 14, "selectedindex": 16, "selector": [10, 16], "selector1": 10, "selector2": 10, "selectorn": 10, "selectwidget": 12, "self": [2, 5, 10, 12, 13, 14, 16], "semant": [12, 13], "send": [2, 6, 12, 13, 17], "send_two_factor_email": 13, "sender": [13, 16], "sendgrid": 16, "sendgrid_api_kei": 16, "sendgridapicli": 16, "sendmail": 16, "sendmail_task": 16, "sens": [0, 6, 12, 13], "sensit": [5, 6, 12], "sent": [5, 6, 13, 16], "separ": [2, 4, 5, 6, 8, 12, 13, 14, 16], "sequenc": [5, 6], "sequenti": 6, "seri": 1, "serial": [4, 5, 6, 8, 10, 12], "serializ": [0, 5, 6, 10], "serv": [0, 2, 4, 6, 13, 15, 16], "server": [0, 2, 3, 4, 7, 8, 13, 15, 16, 17], "server_addr": 6, "server_nam": 4, "serversid": 16, "session": [0, 2, 4, 6, 10, 12, 13, 14, 15, 16, 17], "session_app1": 5, "session_secret_kei": 5, "set": [0, 1, 2, 4, 5, 7, 8, 10, 11, 13, 16], "set_attribut": 6, "set_encod": 6, "set_head": 6, "set_password": 3, "setinterv": 16, "setup": [1, 3, 4, 13, 14, 16, 17], "setvirtualfield": 6, "sever": [4, 6], "sf": 16, "sftp": 6, "sg": 16, "sh": 16, "sha512": [8, 12], "share": [6, 15], "shell": 10, "ship": 8, "shoe": 6, "shop": 5, "short": [12, 16], "shortcut": [8, 12], "shorter": [6, 12], "shortli": 4, "should": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "show": [1, 2, 4, 5, 6, 8, 12, 13, 14], "show_id": 14, "showcas": 6, "shown": [2, 6, 12, 13], "shutil": 6, "side": [1, 6, 13, 16, 17], "sidebar": 8, "sidebar_en": 8, "sidebar_menu": 10, "sidecar": 16, "sign": [2, 4, 5, 6, 8, 12, 13], "signatur": [5, 7, 10, 12, 16], "signed_url": 5, "signer": 5, "signifi": 8, "signific": 8, "signing_info": 12, "similar": [0, 4, 5, 6, 10, 12, 15], "similarli": [6, 10], "simpl": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 16, 17], "simple_queri": 16, "simpler": [6, 8, 14], "simplest": [2, 4], "simpli": [2, 4, 5, 6, 8, 11, 12, 16], "simplic": [6, 16], "simplifi": [0, 4, 6, 8], "simultan": 6, "sinc": [0, 4, 5, 6, 7, 8, 13, 14, 15], "singl": [0, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14], "singleton": [5, 15], "site": [1, 2, 4, 5, 6, 10, 14, 16], "situat": 6, "six": 6, "size": [5, 6, 8, 12], "skip": [5, 16], "slash": [1, 4, 5, 13], "slave": 6, "sleep": 16, "sleep_tim": 16, "slice": 6, "slicker": 0, "slow": [6, 16], "slug": 12, "sm": 16, "small": [5, 12], "smaller": 6, "smtplib": 16, "snippet": 6, "so": [2, 5, 6, 8, 10, 12, 13, 14, 15, 16], "soap": 10, "socket": 16, "solut": [0, 1, 2, 6, 16], "some": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 15, 16], "some_condit": 8, "some_form": 12, "some_valu": 6, "somefield": 6, "somefil": 6, "somepath": 5, "somet": 6, "someth": [5, 6, 7, 8, 10, 12, 13, 14, 16], "sometim": [5, 6, 8, 10, 12, 13, 14], "somevalu": 6, "somewhat": 12, "somewher": [5, 6], "soon": [8, 16], "sophist": 15, "sort": 14, "sourc": [3, 4, 6, 12, 16], "source1": 12, "south": 1, "sp": 6, "space": [6, 8, 12, 14], "span": [4, 15], "spatialit": 6, "speak": 5, "special": [0, 4, 5, 6, 8, 10, 11, 13, 15, 17], "specif": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 15, 16], "specifi": [2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "speed": [6, 7, 8], "sphinx": 1, "spiderman": [7, 14], "spin": [0, 4], "spirit": 7, "split": [6, 7, 8, 12], "split_email": 12, "sql": 17, "sql_mode": 6, "sqladapt": 6, "sqlcustomtyp": 6, "sqlform": [0, 12, 15], "sqlite": [1, 5, 7, 13, 14, 16], "sqlite3": 6, "sqliteadapt": 6, "squar": [0, 8], "src": [6, 8, 10, 12, 16], "ss": 12, "ssl": [2, 4], "ssl_cert": 2, "ssl_kei": 2, "sslcert": 6, "sslkei": 6, "sslmode": 6, "sslrootcert": 6, "sso_id": [5, 13], "stabl": 2, "stai": 6, "stand": [7, 10, 12], "standard": [1, 2, 3, 6, 7, 13, 14, 15, 16, 17], "start": [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "start_imperson": 13, "startup": [6, 15, 17], "state": [5, 6, 7, 15, 16], "stateless": [5, 16], "statement": [6, 8, 12], "static": [1, 8, 10, 15, 17], "static_dev": 4, "statu": [7, 16], "status_cod": 16, "stderr": [2, 16], "stdout": [2, 16], "steil": [0, 1, 14], "step": [2, 6, 13, 15, 16], "step1": 5, "step2": 5, "step3": 5, "step_complet": 5, "still": [2, 5, 6, 10, 12, 14, 15], "stone": 14, "stop": [2, 3, 13], "stop_imperson": 13, "storag": [5, 6, 7, 14], "store": [0, 2, 5, 6, 10, 12, 13, 15, 16], "stored_item": 6, "stored_item_arch": 6, "stori": [0, 5], "str": [2, 5, 6, 10, 12, 15, 16], "stream": [4, 5, 6, 15], "strength": 7, "strict": 12, "strictli": [4, 6, 16], "string": [4, 5, 7, 8, 10, 11, 12, 14], "stringio": 6, "stringlistproperti": 6, "strip": [2, 12, 15], "strong": [0, 10, 13], "strongli": [1, 5, 12, 14], "structur": [1, 4, 5, 6, 11, 13, 14, 15, 17], "stuck": 2, "student": 2, "studi": 1, "studio": 1, "stuff": [12, 14], "style": [4, 8, 12, 16, 17], "stylesheet": [8, 14], "sub": 6, "subclass": [6, 12], "subfold": [4, 5, 6], "subhead": 10, "subject": [6, 7, 13, 16], "submiss": [12, 16], "submit": [1, 5, 6, 10, 12, 13, 14, 16], "submodul": 0, "subnet": 12, "subqueri": 16, "subsect": 6, "subset": [0, 6, 12], "substitut": [6, 8], "substr": 12, "subtl": 6, "succe": 6, "succed": 13, "succeed": 0, "success": [0, 4, 5, 6, 7, 13, 16], "successfulli": 13, "sudo": [2, 13], "suffer": [0, 13], "suffic": 6, "suffici": [6, 13], "sugar": 5, "suggest": [1, 6, 12], "sugizo": 0, "suit": 2, "summar": 6, "superhero": [6, 7, 12, 14, 16], "superman": [6, 7, 12, 14], "superpow": [6, 7], "superseed": 6, "suppli": 10, "support": [1, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 17], "suppos": 6, "suppress": [2, 10], "sure": [2, 3, 4, 5, 6, 11, 12, 13, 16], "surround": 12, "susan": 6, "sv": 6, "switch": [2, 5, 6], "sybas": 6, "sybaseadapt": 6, "symbol": 12, "symlink": [2, 4], "sync": [2, 6], "synopsi": 12, "syntact": [5, 12], "syntax": [0, 1, 4, 5, 6, 7, 10, 12, 13, 15, 16, 17], "system": [0, 2, 5, 6, 10, 13, 16], "sysus": 6, "t": [0, 1, 2, 3, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15], "t_folder": 5, "tab": [3, 5, 12], "tabl": [5, 7, 12, 13, 14, 15, 16], "table1": 6, "table_hash": 6, "table_nam": 6, "tablenam": [6, 7, 12, 16], "tag": [0, 5, 7, 8, 15, 16, 17], "tag_input": 16, "tagged_db": 13, "tagger": 10, "tags_input": 16, "tail": 6, "tail_nam": 13, "take": [2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 16], "taken": 12, "talk": 4, "tamper": [5, 6], "tanti": 16, "tantissimi": 11, "tap": 16, "tar": 12, "target": [6, 10, 16], "task": [1, 5, 6, 13, 17], "task_run": 16, "tast": 6, "tbodi": 6, "tcp": 3, "td": 6, "teacher": 13, "technic": 16, "tell": [2, 4, 5, 6, 13, 14, 16], "temp": 6, "templat": [0, 10, 12, 13, 15, 16, 17], "temporari": 6, "temporarili": 5, "ten": 12, "tenanc": 6, "teradata": 6, "teradataadapt": 6, "teredo": 12, "term": [6, 12], "termin": [4, 8, 16], "test": [0, 2, 4, 6, 8, 10, 12, 13, 14], "text": [2, 5, 6, 8, 10, 14, 16], "textarea": 12, "textareawidget": 12, "textual": 10, "th": 6, "than": [0, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 16], "thank": [0, 6, 10], "that_templ": 8, "thead": 6, "thei": [0, 2, 4, 5, 6, 7, 8, 12, 13, 15, 16], "them": [0, 1, 2, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "themselv": [6, 8], "therefor": [2, 4, 5, 6, 8, 10, 12, 15], "therein": 6, "thi": [0, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "thing": [2, 5, 6, 12, 14, 15], "thing_id": 12, "thing_tags_default": 6, "think": [5, 6, 8, 14, 16], "third": [0, 5, 6, 12], "this_templ": 8, "thisisatest": 10, "thisisthekei": 12, "those": [0, 2, 5, 6, 8, 10, 13, 15, 16], "though": 8, "thought": [0, 6, 13], "thread": [2, 5, 6, 15, 16], "threadsafevari": 5, "three": [0, 5, 6, 13], "through": [2, 12], "throughout": 5, "thu": [6, 8], "thumbnail": 12, "ti": 5, "ticket": [0, 3, 6], "tickets_onli": 2, "tild": 6, "tim": 6, "time": [0, 2, 4, 5, 8, 13, 14, 15, 16], "timedelta": 12, "timeoffset": 10, "timeout": [5, 16], "timestamp": [5, 7, 16], "tip": [2, 14, 17], "titl": [3, 8, 12, 16], "tmp": [5, 6], "to_addr": 16, "todai": [0, 12, 16], "todo": 16, "togeth": [0, 6, 8, 11, 14], "toi": 6, "token": [5, 12], "too": [2, 5, 6, 12, 16], "took": 6, "tool": [0, 6, 13, 16], "top": [8, 11, 12, 13], "topic": [14, 17], "tornado": 2, "total": [6, 7], "total_pric": 6, "totp": 13, "touch": 8, "tr": 6, "traceback": [5, 6], "track": [1, 6, 15], "trade": 6, "tradit": [2, 6], "trail": [2, 12], "train": 1, "transact": [5, 16], "transform": [5, 6, 12, 16], "transit": 16, "translat": [1, 2, 4, 6, 8, 12, 15, 16, 17], "transpar": [6, 8, 12], "trap": 16, "treat": [6, 15], "tree": [4, 8, 12], "tri": [0, 6, 13, 15], "trick": 2, "trickeri": 8, "trigger": [3, 5, 6, 16], "trivial": [5, 6], "true": [1, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "truncat": 6, "truth": 16, "try": [1, 2, 5, 6, 12, 13, 16], "ttl": 5, "tupl": [6, 8, 10], "turn": [0, 4, 6, 12, 13, 14, 16], "tutori": [2, 14, 16], "twice": [5, 6], "twilio": 16, "twitter": [0, 5, 13], "two": [0, 1, 2, 4, 5, 6, 8, 12, 14, 15, 16], "two_factor": 13, "txt": [0, 2, 4, 6], "type": [2, 4, 5, 7, 8, 10, 13, 14, 16], "typewrit": 10, "typic": [4, 5, 6, 8, 12], "u": [2, 6, 10, 12], "ubuntu": 13, "uc": 1, "ui": 5, "uid": 6, "ul": [8, 12, 13, 16], "un": [8, 10, 11, 16], "unari": 6, "unauthent": [5, 6, 10], "unauthor": 6, "unchang": [6, 12], "undefin": 5, "under": [2, 3, 4, 5, 6, 12], "underli": 15, "underscor": [6, 10, 12], "understand": [0, 1, 4, 6, 7, 8, 14, 17], "undocu": 16, "unfortun": [6, 14], "unicod": [6, 12], "unicodedecodeerror": 6, "uniform": 6, "union": 6, "uniqu": [6, 7, 12], "unit_pric": 6, "univers": [6, 10], "unknown": 4, "unless": [2, 3, 5, 6, 8, 12, 16], "unlik": [0, 2, 4, 6, 7, 8, 15, 16], "unnam": [6, 10], "unned": 2, "unord": 10, "unpkg": 16, "unquot": [6, 10], "unsaf": [5, 10, 12], "untest": [2, 13], "until": [0, 5, 6, 8, 12], "unus": 6, "unusu": 6, "unwant": [2, 6], "unzip": 2, "up": [1, 2, 5, 6, 8, 13, 16], "updat": [2, 5, 12, 14, 15, 16, 17], "update_form": 12, "update_languag": 11, "update_na": 6, "update_th": 12, "upgrad": [6, 17], "upload": [0, 4, 15], "upload_fold": [6, 12], "upload_help": 16, "uploadf": 6, "uploadfield": 6, "uploadfold": 6, "uploadsepar": 6, "upon": [2, 8, 13, 15], "upper": [4, 5, 12], "upper_cas": 5, "uppercas": [5, 12], "uri": 13, "url": [2, 4, 5, 7, 8, 12, 13, 14, 15, 16], "url_prefix": 2, "url_sign": 5, "url_to_post_to": 16, "urlsign": 17, "us": [0, 1, 3, 4, 7, 11, 12, 15, 17], "usabl": 14, "usag": [2, 3, 4, 5, 6, 10, 12, 13, 14], "use_schedul": 16, "useful": 12, "useless": [12, 15], "user": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 15, 16], "user_email": 15, "user_id": [5, 6, 13, 15], "user_nam": 6, "user_outside_network": 13, "user_password": 6, "user_token": 6, "usernam": [5, 6, 13], "usr": 1, "usual": [1, 2, 5, 6, 7, 8, 10, 12], "utcnow": [5, 6], "utf": 6, "utf8": 6, "utf8mb4": 6, "util": [2, 4, 5, 8, 10, 12, 13, 14, 15, 17], "uuid": [5, 6], "uuid4": [5, 6], "v": [5, 10, 12, 16], "v3": 1, "val": [14, 16], "val1_row1": 6, "val1_row2": 6, "val2_row1": 6, "val2_row2": 6, "valid": [2, 4, 7, 8, 10, 13, 14, 15, 17], "validate_cod": 13, "validate_j": 4, "valq7711": [0, 4], "valu": [2, 5, 7, 8, 10, 11, 12, 13, 14, 16], "valuabl": 1, "value1": 6, "value2": 6, "value_field": 12, "var": [6, 10, 12, 15, 16], "varchar": 6, "variabl": [4, 5, 6, 10, 11, 12, 14, 16], "variou": [5, 6], "ve": [1, 2, 3, 5, 8, 10, 12, 13, 14, 16], "vehicl": 5, "vendor": 16, "vendor_typ": 16, "venv": 2, "verbos": 6, "veri": [0, 4, 5, 6, 8, 10, 12, 13, 15, 16], "verif": 13, "verifi": [5, 12, 13, 16], "verify_email": 13, "versa": 5, "version": [1, 3, 5, 7, 8, 12, 16], "vertica": 6, "verticaadapt": 6, "via": [5, 6, 8, 10, 13, 16], "vice": 5, "video": [2, 16], "view": [1, 5, 6], "viewport": 8, "virtual": [1, 17], "virtualenv": [1, 2], "virtualfield": 6, "visibl": 4, "visit": [3, 5, 6, 8, 12, 13], "visit_log": 5, "visitor": [6, 10, 12], "visto": 5, "visual": 1, "vital": 13, "volt": 5, "vscode": 2, "vue": [0, 4, 16], "vulner": 8, "w": [2, 4, 5, 6], "w2p_even": 6, "w2p_odd": 6, "wa": [0, 5, 6, 12, 13, 14], "wai": [0, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "wait": 6, "waitress": 2, "want": [2, 4, 5, 6, 8, 10, 12, 14, 16], "warn": [2, 5], "warp": 11, "watch": [2, 3, 17], "wayn": [7, 14], "wb": 6, "we": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "web": [0, 1, 2, 5, 6, 8, 13, 14, 15, 16, 17], "web2pi": [0, 1, 2, 3, 4, 5, 6, 12, 13, 14, 17], "webserv": 4, "websit": [6, 16], "websocket": 16, "welcom": [2, 4, 5, 8, 15, 16], "well": [0, 5, 6, 8, 12, 13, 16], "were": [0, 4, 6], "what": [1, 4, 5, 6, 8, 12, 13, 15, 16, 17], "whatev": [10, 16], "when": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "whenev": [4, 6], "where": [1, 2, 4, 6, 7, 8, 12, 13, 15, 16], "whether": [5, 6, 8, 12, 13, 14, 15], "which": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "whichev": 16, "while": [0, 4, 5, 6, 7, 12, 13, 14, 15], "white": [10, 16], "whitelist": 13, "who": [0, 13], "whole": [4, 12], "whose": [6, 8, 12], "why": [5, 6, 8, 15], "widget": [6, 17], "width": [8, 12], "wiki": [6, 7], "wikipedia": [6, 7], "wild": 6, "wildcard": 4, "william": 6, "window": [1, 2, 4, 5, 6, 8, 12], "wish": [1, 6, 12, 16], "wit": 2, "with_alia": 6, "within": [0, 1, 4, 5, 6, 8, 10, 12, 14, 16], "without": [1, 8, 10, 14, 16, 17], "wolf": 0, "won": 6, "wood": 6, "word": [5, 6, 11, 14], "work": [0, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "worker": [2, 5, 16], "workflow": [5, 17], "workload": 6, "workspacefold": 2, "world": [4, 5, 6, 8, 10, 12, 16], "worri": [4, 6], "worth": 8, "would": [0, 4, 5, 6, 8, 10, 12, 13, 14, 16], "wouldn": 6, "wrap": [5, 11, 12, 14, 16], "wrapper": [15, 16], "writabl": [5, 6, 12, 15], "write": [2, 4, 5, 6, 8, 12, 15], "written": [1, 8, 12, 14], "wrong": [0, 5, 6], "wsgi": 5, "wsgiref": 2, "wsgirefthreadingserv": 2, "wsgith": 2, "www": [2, 8, 10, 14, 16], "x": [2, 4, 6, 8, 10, 12, 14, 16], "xml": [8, 12, 15, 16], "xmlescap": 10, "xmln": 10, "xss": [6, 8, 10], "xyz": [10, 12], "y": [2, 10, 12], "yaml": 2, "yatl": [0, 4, 5, 6, 12, 14, 16, 17], "yb": 10, "ye": [2, 6, 16], "year": [12, 16], "yes_or_no": 6, "yet": [0, 2, 4, 5, 6, 8, 12, 16], "yield": 6, "yml": 2, "you": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "your": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "your_app": 12, "your_full_path_to_py4web": 1, "your_nam": [1, 16], "yourapp": 6, "yourappnam": 2, "youremail": 13, "yourself": [1, 2, 12, 14], "youtub": [2, 6], "yyyi": 12, "z": [10, 12], "zanferrari": 0, "zap": 13, "zap_id": 13, "zapper": 13, "zero": [6, 8, 12, 14], "zip": [0, 2, 6, 12], "zip_cod": 16, "zxjdbc": 6}, "titles": ["What is py4web?", "Help, resources and hints", "Installation and Startup", "The Dashboard", "Creating an app", "Fixtures", "The Database Abstraction Layer (DAL)", "The RestAPI", "YATL Template Language", "<no title>", "YATL helpers", "Internationalization", "Forms", "Authentication and authorization", "Grid", "From web2py to py4web", "Advanced topics and examples", "py4web: the reference Manual"], "titleterms": {"A": [1, 10, 12], "On": 4, "One": 6, "The": [1, 3, 4, 5, 6, 7, 12, 14, 16], "_lastsql": 6, "_scaffold": 4, "about": [5, 6], "abstract": 6, "access": 15, "acknowledg": 0, "action": [7, 13, 14], "ad": 6, "adapt": 6, "advanc": [6, 12, 16], "aka": 2, "alias": 6, "all": 6, "alon": 6, "an": [4, 6], "anoth": 6, "any_of": 12, "anywher": 5, "app": [2, 4, 6], "applic": 6, "arg": 15, "as_dict": 6, "as_list": 6, "asyncio": 16, "attempt": 6, "attribut": 6, "auth": [5, 13, 15], "authent": 13, "author": 13, "autocomplet": 16, "avg": 6, "background": 16, "base": 6, "basic": [8, 12, 14], "beautifi": 10, "belong": 6, "binari": 2, "block": 8, "bodi": 10, "broken": 6, "built": 10, "button": 14, "cach": [5, 6], "cacheabl": 6, "call": [2, 15], "callabl": 14, "callback": 6, "cascad": 6, "case": 6, "cat": 10, "caveat": 5, "celeri": 16, "chang": 4, "checkbox": [12, 14], "children": 10, "class": 14, "cleanup": 12, "client": 5, "coalesc": 6, "coalesce_zero": 6, "column": 14, "com": 2, "combin": 6, "command": [2, 6], "commit": 6, "common": 6, "complex": 12, "comput": 6, "condit": 5, "connect": 6, "constructor": [6, 12], "contain": 6, "content": 17, "contribut": 1, "control": 6, "conveni": 5, "convers": 15, "cooki": 5, "copi": [4, 6], "count": 6, "counter": 15, "creat": 4, "crud": 14, "crypt": 12, "csv": 6, "custom": [5, 6, 10, 12, 14], "dai": 6, "dal": [5, 6], "dashboard": [3, 6], "data": 6, "databas": [5, 6, 12], "datastor": 6, "date": 12, "db": 6, "debug": 1, "decor": 5, "def": 8, "default": [6, 8], "defin": 6, "define_t": 6, "delet": 6, "deploy": 2, "design": [2, 12], "develop": 8, "dictionari": [6, 12], "discord": [1, 13], "distinct": 6, "distribut": 6, "div": 10, "docker": 2, "dom": 10, "domain": 4, "drop": 6, "dynam": 4, "elif": 8, "els": 8, "em": 10, "endswith": 6, "engin": 2, "environ": 2, "equal": 12, "exampl": [7, 12, 14, 15, 16], "except": 8, "exclud": 6, "executesql": 6, "experi": 6, "experiment": 6, "export": 6, "express": 6, "extend": 8, "facebook": 13, "factor": 13, "failur": 6, "fake_migr": 6, "featur": [6, 14], "fetch": 6, "field": [6, 12, 14], "file": [4, 11, 12, 15], "filter": [6, 14], "filter_in": 6, "filter_out": 6, "final": 8, "find": [6, 10], "first": [2, 6], "fix": 6, "fixtur": 5, "flash": [5, 15], "folder": 6, "form": [10, 12, 15, 16], "format": [6, 12, 16], "from": [2, 4, 6, 15], "function": [8, 12], "gae": 2, "gcloud": 2, "gener": 6, "get": 7, "github": 1, "global": 2, "googl": [1, 2, 6, 13], "gotcha": 6, "grid": [14, 15, 16], "group": [1, 6], "groupbi": 6, "h1": 10, "h2": 10, "h3": 10, "h4": 10, "h5": 10, "h6": 10, "have": 6, "head": 10, "hello": 15, "help": 1, "helper": 10, "hint": 1, "hour": 6, "how": 1, "html": [6, 10], "htmx": 16, "http": 2, "i": [0, 10], "id": 6, "ilik": 6, "img": 10, "imperson": 13, "import": 6, "includ": 8, "index": 6, "indic": 17, "inform": 8, "inherit": 6, "inject": [5, 10], "inner": 6, "input": 10, "insert": 6, "insid": 13, "instal": 2, "internation": 11, "introduct": 6, "is_alphanumer": 12, "is_dat": 12, "is_date_in_rang": 12, "is_datetim": 12, "is_datetime_in_rang": 12, "is_decimal_in_rang": 12, "is_email": 12, "is_empty_or": 12, "is_equal_to": 12, "is_expr": 12, "is_fil": 12, "is_float_in_rang": 12, "is_imag": 12, "is_in_db": 12, "is_in_set": 12, "is_int_in_rang": 12, "is_ipaddress": 12, "is_ipv4": 12, "is_ipv6": 12, "is_json": 12, "is_length": 12, "is_list_of": 12, "is_list_of_email": 12, "is_low": 12, "is_match": 12, "is_not_empti": 12, "is_not_in_db": 12, "is_null_or": 12, "is_saf": 12, "is_slug": 12, "is_strong": 12, "is_tim": 12, "is_upload_filenam": 12, "is_upp": 12, "is_url": 12, "isempti": 6, "iter": 6, "j": 16, "join": 6, "kei": [6, 14], "keyword": 6, "label": 10, "languag": 8, "last": 6, "layer": 6, "layout": 8, "lazi": 6, "ldap": 13, "left": 6, "legaci": 6, "len": 6, "less": 6, "li": 10, "like": 6, "limitbi": 6, "line": 2, "list": 6, "local": 2, "locat": 6, "logic": 6, "login": 3, "lower": 6, "main": 3, "make": 6, "mani": 6, "manipul": 12, "manual": [1, 17], "map": 4, "max": 6, "memcach": 5, "memoiz": 5, "memori": 6, "messag": 16, "method": [6, 15], "microsoft": 6, "migrat": 6, "min": 6, "minim": 12, "minut": 6, "mobil": 8, "model": 6, "modern": 1, "modif": 6, "month": 6, "more": 6, "mssql": 6, "multipl": [5, 13], "mysql": 6, "name": 6, "new": 6, "new_app": 2, "nosql": 6, "note": 6, "o": 15, "oauth2": 13, "object": [4, 13, 14, 16], "ol": 10, "old": 6, "on_defin": 6, "onc": 6, "one": 6, "oper": 6, "option": [2, 10, 12], "oracl": 6, "orderbi": 6, "orderby_on_limitbi": 6, "other": [6, 12], "outer": 6, "overview": 10, "p": 10, "page": [3, 4, 8], "pam": 13, "paramet": [6, 12, 14], "permiss": 13, "pip": 2, "platform": 2, "plugin": 13, "plural": [6, 11], "podman": 2, "polici": 7, "polymodel": 6, "pool": 6, "practic": 7, "pre": 10, "prerequisit": [1, 2], "primari": 6, "primarykei": 6, "procedur": 2, "py4web": [0, 1, 6, 15, 16, 17], "pycharm": 1, "python": 1, "pythonanywher": 2, "q": 16, "queri": 6, "quick": 6, "quot": 6, "rang": 12, "raw": 6, "real": 6, "record": 6, "recurs": 6, "redefin": 6, "redi": 5, "redirect": 15, "refer": [6, 14, 17], "regexp": 6, "relat": 6, "remot": 6, "render": 6, "replic": 6, "repres": 6, "represent": 6, "request": 4, "reserv": 6, "resourc": 1, "respons": 7, "restapi": 7, "return": [4, 8, 15], "rname": 6, "rollback": 6, "rout": 4, "row": 6, "run": [2, 6], "sampl": 14, "schedul": 16, "scratch": 4, "script": 10, "search": 14, "second": 6, "secur": [6, 12], "select": [6, 10], "self": 6, "send": 16, "sequence_nam": 6, "server": [1, 5, 6, 10], "session": 5, "set": [6, 12, 14, 15], "set_password": 2, "setup": 2, "share": 5, "shell": [2, 6], "shortcut": 6, "side": [5, 10], "sidecar": 12, "signatur": 6, "simpl": 15, "singular": 6, "sort": [6, 12], "sourc": [1, 2], "span": 10, "special": [2, 12], "sql": 6, "sqlite": 6, "stand": 6, "standard": [8, 12], "startswith": 6, "startup": 2, "static": 4, "string": [6, 16], "structur": [8, 12], "style": [6, 10, 14], "substr": 6, "sum": 6, "summari": 6, "super": 8, "support": [2, 6], "synchron": 6, "syntax": 8, "t": 16, "tabl": [6, 10, 17], "table_class": 6, "tag": [6, 10, 12, 13], "task": 16, "tbodi": 10, "td": 10, "templat": [4, 5, 8, 14], "text": 12, "textarea": 10, "th": 10, "thead": 10, "thi": 1, "time": [6, 12], "tip": 1, "titl": 10, "topic": 16, "tour": 6, "tr": 10, "transact": 6, "translat": [5, 11], "trigger_nam": 6, "try": 8, "tt": 10, "tupl": 12, "tutori": 1, "two": 13, "two_factor_requir": 13, "two_factor_send": 13, "two_factor_tri": 13, "two_factor_valid": 13, "type": [6, 12], "ubuntu": 2, "ui": 13, "ul": 10, "understand": 2, "up": 15, "updat": [6, 11], "update_or_insert": 6, "update_record": 6, "upgrad": 2, "upload": [6, 12], "upper": 6, "uri": 6, "url": 10, "urlsign": 5, "us": [2, 5, 6, 8, 10, 13, 14, 16], "usag": 16, "user": 13, "util": 16, "valid": [6, 12], "validate_and_insert": 6, "validate_and_upd": 6, "valu": [4, 6], "variabl": [8, 15], "version": [2, 6], "video": 1, "view": 15, "virtual": [2, 6], "vscode": 1, "watch": 4, "web": [3, 4], "web2pi": 15, "what": 0, "while": 8, "widget": [12, 16], "without": [2, 6, 12], "workflow": 8, "workplac": 1, "world": 15, "wsgi": 2, "xml": [6, 10], "yatl": [8, 10], "year": 6}}) \ No newline at end of file diff --git a/apps/_documentation/static/pt/.buildinfo b/apps/_documentation/static/pt/.buildinfo index b8d235cb5..923462398 100644 --- a/apps/_documentation/static/pt/.buildinfo +++ b/apps/_documentation/static/pt/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 9f04100a6a58698928add7415905ba84 +config: 98265029ce021aefb7b5237442b00c75 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/apps/_documentation/static/pt/_images/first_run.png b/apps/_documentation/static/pt/_images/first_run.png index 59320040c..a670f0150 100644 Binary files a/apps/_documentation/static/pt/_images/first_run.png and b/apps/_documentation/static/pt/_images/first_run.png differ diff --git a/apps/_documentation/static/pt/_images/form1.png b/apps/_documentation/static/pt/_images/form1.png index 6f66b7257..4839a27f9 100644 Binary files a/apps/_documentation/static/pt/_images/form1.png and b/apps/_documentation/static/pt/_images/form1.png differ diff --git a/apps/_documentation/static/pt/_images/form2.png b/apps/_documentation/static/pt/_images/form2.png deleted file mode 100644 index b83647c2f..000000000 Binary files a/apps/_documentation/static/pt/_images/form2.png and /dev/null differ diff --git a/apps/_documentation/static/pt/_images/form3.png b/apps/_documentation/static/pt/_images/form3.png deleted file mode 100644 index 3de9bbba9..000000000 Binary files a/apps/_documentation/static/pt/_images/form3.png and /dev/null differ diff --git a/apps/_documentation/static/pt/_images/form4.png b/apps/_documentation/static/pt/_images/form4.png deleted file mode 100644 index 9534e4683..000000000 Binary files a/apps/_documentation/static/pt/_images/form4.png and /dev/null differ diff --git a/apps/_documentation/static/pt/_images/form5.png b/apps/_documentation/static/pt/_images/form5.png deleted file mode 100644 index 9b88b04ad..000000000 Binary files a/apps/_documentation/static/pt/_images/form5.png and /dev/null differ diff --git a/apps/_documentation/static/pt/_images/form6.png b/apps/_documentation/static/pt/_images/form6.png deleted file mode 100644 index 9761c347e..000000000 Binary files a/apps/_documentation/static/pt/_images/form6.png and /dev/null differ diff --git a/apps/_documentation/static/pt/_static/basic.css b/apps/_documentation/static/pt/_static/basic.css index 7577acb1a..f316efcb4 100644 --- a/apps/_documentation/static/pt/_static/basic.css +++ b/apps/_documentation/static/pt/_static/basic.css @@ -4,7 +4,7 @@ * * Sphinx stylesheet -- basic theme. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -237,6 +237,10 @@ a.headerlink { visibility: hidden; } +a:visited { + color: #551A8B; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -670,6 +674,16 @@ dd { margin-left: 30px; } +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; @@ -738,6 +752,14 @@ abbr, acronym { cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { diff --git a/apps/_documentation/static/pt/_static/css/theme.css b/apps/_documentation/static/pt/_static/css/theme.css index c03c88f06..19a446a0e 100644 --- a/apps/_documentation/static/pt/_static/css/theme.css +++ b/apps/_documentation/static/pt/_static/css/theme.css @@ -1,4 +1,4 @@ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/apps/_documentation/static/pt/_static/doctools.js b/apps/_documentation/static/pt/_static/doctools.js index d06a71d75..4d67807d1 100644 --- a/apps/_documentation/static/pt/_static/doctools.js +++ b/apps/_documentation/static/pt/_static/doctools.js @@ -4,7 +4,7 @@ * * Base JavaScript utilities for all Sphinx HTML documentation. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ diff --git a/apps/_documentation/static/pt/_static/documentation_options.js b/apps/_documentation/static/pt/_static/documentation_options.js index 131e1834e..c0519342e 100644 --- a/apps/_documentation/static/pt/_static/documentation_options.js +++ b/apps/_documentation/static/pt/_static/documentation_options.js @@ -1,6 +1,5 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.20230507.1', +const DOCUMENTATION_OPTIONS = { + VERSION: '20240915', LANGUAGE: 'pt', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/apps/_documentation/static/pt/_static/language_data.js b/apps/_documentation/static/pt/_static/language_data.js index a5788054d..47f42f25f 100644 --- a/apps/_documentation/static/pt/_static/language_data.js +++ b/apps/_documentation/static/pt/_static/language_data.js @@ -5,7 +5,7 @@ * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @@ -13,7 +13,7 @@ var stopwords = ["a", "ao", "aos", "aquela", "aquelas", "aquele", "aqueles", "aquilo", "as", "at\u00e9", "com", "como", "da", "das", "de", "dela", "delas", "dele", "deles", "depois", "do", "dos", "e", "ela", "elas", "ele", "eles", "em", "entre", "era", "eram", "essa", "essas", "esse", "esses", "esta", "estamos", "estas", "estava", "estavam", "este", "esteja", "estejam", "estejamos", "estes", "esteve", "estive", "estivemos", "estiver", "estivera", "estiveram", "estiverem", "estivermos", "estivesse", "estivessem", "estiv\u00e9ramos", "estiv\u00e9ssemos", "estou", "est\u00e1", "est\u00e1vamos", "est\u00e3o", "eu", "foi", "fomos", "for", "fora", "foram", "forem", "formos", "fosse", "fossem", "fui", "f\u00f4ramos", "f\u00f4ssemos", "haja", "hajam", "hajamos", "havemos", "hei", "houve", "houvemos", "houver", "houvera", "houveram", "houverei", "houverem", "houveremos", "houveria", "houveriam", "houvermos", "houver\u00e1", "houver\u00e3o", "houver\u00edamos", "houvesse", "houvessem", "houv\u00e9ramos", "houv\u00e9ssemos", "h\u00e1", "h\u00e3o", "isso", "isto", "j\u00e1", "lhe", "lhes", "mais", "mas", "me", "mesmo", "meu", "meus", "minha", "minhas", "muito", "na", "nas", "nem", "no", "nos", "nossa", "nossas", "nosso", "nossos", "num", "numa", "n\u00e3o", "n\u00f3s", "o", "os", "ou", "para", "pela", "pelas", "pelo", "pelos", "por", "qual", "quando", "que", "quem", "se", "seja", "sejam", "sejamos", "sem", "serei", "seremos", "seria", "seriam", "ser\u00e1", "ser\u00e3o", "ser\u00edamos", "seu", "seus", "somos", "sou", "sua", "suas", "s\u00e3o", "s\u00f3", "tamb\u00e9m", "te", "tem", "temos", "tenha", "tenham", "tenhamos", "tenho", "terei", "teremos", "teria", "teriam", "ter\u00e1", "ter\u00e3o", "ter\u00edamos", "teu", "teus", "teve", "tinha", "tinham", "tive", "tivemos", "tiver", "tivera", "tiveram", "tiverem", "tivermos", "tivesse", "tivessem", "tiv\u00e9ramos", "tiv\u00e9ssemos", "tu", "tua", "tuas", "t\u00e9m", "t\u00ednhamos", "um", "uma", "voc\u00ea", "voc\u00eas", "vos", "\u00e0", "\u00e0s", "\u00e9ramos"]; -/* Non-minified version is copied as a separate JS file, is available */ +/* Non-minified version is copied as a separate JS file, if available */ BaseStemmer=function(){this.setCurrent=function(r){this.current=r;this.cursor=0;this.limit=this.current.length;this.limit_backward=0;this.bra=this.cursor;this.ket=this.limit};this.getCurrent=function(){return this.current};this.copy_from=function(r){this.current=r.current;this.cursor=r.cursor;this.limit=r.limit;this.limit_backward=r.limit_backward;this.bra=r.bra;this.ket=r.ket};this.in_grouping=function(r,t,i){if(this.cursor>=this.limit)return false;var s=this.current.charCodeAt(this.cursor);if(s>i||s>>3]&1<<(s&7))==0)return false;this.cursor++;return true};this.in_grouping_b=function(r,t,i){if(this.cursor<=this.limit_backward)return false;var s=this.current.charCodeAt(this.cursor-1);if(s>i||s>>3]&1<<(s&7))==0)return false;this.cursor--;return true};this.out_grouping=function(r,t,i){if(this.cursor>=this.limit)return false;var s=this.current.charCodeAt(this.cursor);if(s>i||s>>3]&1<<(s&7))==0){this.cursor++;return true}return false};this.out_grouping_b=function(r,t,i){if(this.cursor<=this.limit_backward)return false;var s=this.current.charCodeAt(this.cursor-1);if(s>i||s>>3]&1<<(s&7))==0){this.cursor--;return true}return false};this.eq_s=function(r){if(this.limit-this.cursor>>1);var a=0;var f=h0)break;if(i==t)break;if(n)break;n=true}}do{var l=r[t];if(h>=l[0].length){this.cursor=s+l[0].length;if(l.length<4)return l[2];var v=l[3](this);this.cursor=s+l[0].length;if(v)return l[2]}t=l[1]}while(t>=0);return 0};this.find_among_b=function(r){var t=0;var i=r.length;var s=this.cursor;var e=this.limit_backward;var h=0;var u=0;var n=false;while(true){var c=t+(i-t>>1);var a=0;var f=h=0;o--){if(s-f==e){a=-1;break}a=this.current.charCodeAt(s-1-f)-l[0].charCodeAt(o);if(a!=0)break;f++}if(a<0){i=c;u=f}else{t=c;h=f}if(i-t<=1){if(t>0)break;if(i==t)break;if(n)break;n=true}}do{var l=r[t];if(h>=l[0].length){this.cursor=s-l[0].length;if(l.length<4)return l[2];var v=l[3](this);this.cursor=s-l[0].length;if(v)return l[2]}t=l[1]}while(t>=0);return 0};this.replace_s=function(r,t,i){var s=i.length-(t-r);this.current=this.current.slice(0,r)+i+this.current.slice(t);this.limit+=s;if(this.cursor>=t)this.cursor+=s;else if(this.cursor>r)this.cursor=r;return s};this.slice_check=function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>this.current.length){return false}return true};this.slice_from=function(r){var t=false;if(this.slice_check()){this.replace_s(this.bra,this.ket,r);t=true}return t};this.slice_del=function(){return this.slice_from("")};this.insert=function(r,t,i){var s=this.replace_s(r,t,i);if(r<=this.bra)this.bra+=s;if(r<=this.ket)this.ket+=s};this.slice_to=function(){var r="";if(this.slice_check()){r=this.current.slice(this.bra,this.ket)}return r};this.assign_to=function(){return this.current.slice(0,this.limit)}}; PortugueseStemmer=function(){var r=new BaseStemmer;var e=[["",-1,3],["ã",0,1],["õ",0,2]];var i=[["",-1,3],["a~",0,1],["o~",0,2]];var s=[["ic",-1,-1],["ad",-1,-1],["os",-1,-1],["iv",-1,1]];var a=[["ante",-1,1],["avel",-1,1],["ível",-1,1]];var u=[["ic",-1,1],["abil",-1,1],["iv",-1,1]];var o=[["ica",-1,1],["ância",-1,1],["ência",-1,4],["logia",-1,2],["ira",-1,9],["adora",-1,1],["osa",-1,1],["ista",-1,1],["iva",-1,8],["eza",-1,1],["idade",-1,7],["ante",-1,1],["mente",-1,6],["amente",12,5],["ável",-1,1],["ível",-1,1],["ico",-1,1],["ismo",-1,1],["oso",-1,1],["amento",-1,1],["imento",-1,1],["ivo",-1,8],["aça~o",-1,1],["uça~o",-1,3],["ador",-1,1],["icas",-1,1],["ências",-1,4],["logias",-1,2],["iras",-1,9],["adoras",-1,1],["osas",-1,1],["istas",-1,1],["ivas",-1,8],["ezas",-1,1],["idades",-1,7],["adores",-1,1],["antes",-1,1],["aço~es",-1,1],["uço~es",-1,3],["icos",-1,1],["ismos",-1,1],["osos",-1,1],["amentos",-1,1],["imentos",-1,1],["ivos",-1,8]];var t=[["ada",-1,1],["ida",-1,1],["ia",-1,1],["aria",2,1],["eria",2,1],["iria",2,1],["ara",-1,1],["era",-1,1],["ira",-1,1],["ava",-1,1],["asse",-1,1],["esse",-1,1],["isse",-1,1],["aste",-1,1],["este",-1,1],["iste",-1,1],["ei",-1,1],["arei",16,1],["erei",16,1],["irei",16,1],["am",-1,1],["iam",20,1],["ariam",21,1],["eriam",21,1],["iriam",21,1],["aram",20,1],["eram",20,1],["iram",20,1],["avam",20,1],["em",-1,1],["arem",29,1],["erem",29,1],["irem",29,1],["assem",29,1],["essem",29,1],["issem",29,1],["ado",-1,1],["ido",-1,1],["ando",-1,1],["endo",-1,1],["indo",-1,1],["ara~o",-1,1],["era~o",-1,1],["ira~o",-1,1],["ar",-1,1],["er",-1,1],["ir",-1,1],["as",-1,1],["adas",47,1],["idas",47,1],["ias",47,1],["arias",50,1],["erias",50,1],["irias",50,1],["aras",47,1],["eras",47,1],["iras",47,1],["avas",47,1],["es",-1,1],["ardes",58,1],["erdes",58,1],["irdes",58,1],["ares",58,1],["eres",58,1],["ires",58,1],["asses",58,1],["esses",58,1],["isses",58,1],["astes",58,1],["estes",58,1],["istes",58,1],["is",-1,1],["ais",71,1],["eis",71,1],["areis",73,1],["ereis",73,1],["ireis",73,1],["áreis",73,1],["éreis",73,1],["íreis",73,1],["ásseis",73,1],["ésseis",73,1],["ísseis",73,1],["áveis",73,1],["íeis",73,1],["aríeis",84,1],["eríeis",84,1],["iríeis",84,1],["ados",-1,1],["idos",-1,1],["amos",-1,1],["áramos",90,1],["éramos",90,1],["íramos",90,1],["ávamos",90,1],["íamos",90,1],["aríamos",95,1],["eríamos",95,1],["iríamos",95,1],["emos",-1,1],["aremos",99,1],["eremos",99,1],["iremos",99,1],["ássemos",99,1],["êssemos",99,1],["íssemos",99,1],["imos",-1,1],["armos",-1,1],["ermos",-1,1],["irmos",-1,1],["ámos",-1,1],["arás",-1,1],["erás",-1,1],["irás",-1,1],["eu",-1,1],["iu",-1,1],["ou",-1,1],["ará",-1,1],["erá",-1,1],["irá",-1,1]];var c=[["a",-1,1],["i",-1,1],["o",-1,1],["os",-1,1],["á",-1,1],["í",-1,1],["ó",-1,1]];var f=[["e",-1,1],["ç",-1,2],["é",-1,1],["ê",-1,1]];var l=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2];var n=0;var m=0;var b=0;function k(){var i;while(true){var s=r.cursor;r:{r.bra=r.cursor;i=r.find_among(e);if(i==0){break r}r.ket=r.cursor;switch(i){case 1:if(!r.slice_from("a~")){return false}break;case 2:if(!r.slice_from("o~")){return false}break;case 3:if(r.cursor>=r.limit){break r}r.cursor++;break}continue}r.cursor=s;break}return true}function _(){b=r.limit;m=r.limit;n=r.limit;var e=r.cursor;r:{e:{var i=r.cursor;i:{if(!r.in_grouping(l,97,250)){break i}s:{var s=r.cursor;a:{if(!r.out_grouping(l,97,250)){break a}u:while(true){o:{if(!r.in_grouping(l,97,250)){break o}break u}if(r.cursor>=r.limit){break a}r.cursor++}break s}r.cursor=s;if(!r.in_grouping(l,97,250)){break i}a:while(true){u:{if(!r.out_grouping(l,97,250)){break u}break a}if(r.cursor>=r.limit){break i}r.cursor++}}break e}r.cursor=i;if(!r.out_grouping(l,97,250)){break r}i:{var a=r.cursor;s:{if(!r.out_grouping(l,97,250)){break s}a:while(true){u:{if(!r.in_grouping(l,97,250)){break u}break a}if(r.cursor>=r.limit){break s}r.cursor++}break i}r.cursor=a;if(!r.in_grouping(l,97,250)){break r}if(r.cursor>=r.limit){break r}r.cursor++}}b=r.cursor}r.cursor=e;var u=r.cursor;r:{e:while(true){i:{if(!r.in_grouping(l,97,250)){break i}break e}if(r.cursor>=r.limit){break r}r.cursor++}e:while(true){i:{if(!r.out_grouping(l,97,250)){break i}break e}if(r.cursor>=r.limit){break r}r.cursor++}m=r.cursor;e:while(true){i:{if(!r.in_grouping(l,97,250)){break i}break e}if(r.cursor>=r.limit){break r}r.cursor++}e:while(true){i:{if(!r.out_grouping(l,97,250)){break i}break e}if(r.cursor>=r.limit){break r}r.cursor++}n=r.cursor}r.cursor=u;return true}function v(){var e;while(true){var s=r.cursor;r:{r.bra=r.cursor;e=r.find_among(i);if(e==0){break r}r.ket=r.cursor;switch(e){case 1:if(!r.slice_from("ã")){return false}break;case 2:if(!r.slice_from("õ")){return false}break;case 3:if(r.cursor>=r.limit){break r}r.cursor++;break}continue}r.cursor=s;break}return true}function d(){if(!(b<=r.cursor)){return false}return true}function g(){if(!(m<=r.cursor)){return false}return true}function w(){if(!(n<=r.cursor)){return false}return true}function h(){var e;r.ket=r.cursor;e=r.find_among_b(o);if(e==0){return false}r.bra=r.cursor;switch(e){case 1:if(!w()){return false}if(!r.slice_del()){return false}break;case 2:if(!w()){return false}if(!r.slice_from("log")){return false}break;case 3:if(!w()){return false}if(!r.slice_from("u")){return false}break;case 4:if(!w()){return false}if(!r.slice_from("ente")){return false}break;case 5:if(!g()){return false}if(!r.slice_del()){return false}var i=r.limit-r.cursor;r:{r.ket=r.cursor;e=r.find_among_b(s);if(e==0){r.cursor=r.limit-i;break r}r.bra=r.cursor;if(!w()){r.cursor=r.limit-i;break r}if(!r.slice_del()){return false}switch(e){case 1:r.ket=r.cursor;if(!r.eq_s_b("at")){r.cursor=r.limit-i;break r}r.bra=r.cursor;if(!w()){r.cursor=r.limit-i;break r}if(!r.slice_del()){return false}break}}break;case 6:if(!w()){return false}if(!r.slice_del()){return false}var t=r.limit-r.cursor;r:{r.ket=r.cursor;if(r.find_among_b(a)==0){r.cursor=r.limit-t;break r}r.bra=r.cursor;if(!w()){r.cursor=r.limit-t;break r}if(!r.slice_del()){return false}}break;case 7:if(!w()){return false}if(!r.slice_del()){return false}var c=r.limit-r.cursor;r:{r.ket=r.cursor;if(r.find_among_b(u)==0){r.cursor=r.limit-c;break r}r.bra=r.cursor;if(!w()){r.cursor=r.limit-c;break r}if(!r.slice_del()){return false}}break;case 8:if(!w()){return false}if(!r.slice_del()){return false}var f=r.limit-r.cursor;r:{r.ket=r.cursor;if(!r.eq_s_b("at")){r.cursor=r.limit-f;break r}r.bra=r.cursor;if(!w()){r.cursor=r.limit-f;break r}if(!r.slice_del()){return false}}break;case 9:if(!d()){return false}if(!r.eq_s_b("e")){return false}if(!r.slice_from("ir")){return false}break}return true}function p(){if(r.cursor { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms) => { +const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; const [docName, title, anchor, descr, score, _filename] = item; @@ -75,28 +75,35 @@ const _displayItem = (item, searchTerms) => { if (dirname.match(/\/index\/$/)) dirname = dirname.substring(0, dirname.length - 6); else if (dirname === "index/") dirname = ""; - requestUrl = docUrlRoot + dirname; + requestUrl = contentRoot + dirname; linkUrl = requestUrl; } else { // normal html builders - requestUrl = docUrlRoot + docName + docFileSuffix; + requestUrl = contentRoot + docName + docFileSuffix; linkUrl = docName + docLinkSuffix; } let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; linkEl.innerHTML = title; - if (descr) + if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) + Search.makeSearchSummary(data, searchTerms, anchor) ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); }); Search.output.appendChild(listItem); }; @@ -109,26 +116,43 @@ const _finishSearch = (resultCount) => { ); else Search.status.innerText = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( results, resultCount, - searchTerms + searchTerms, + highlightTerms, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms); + _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), 5 ); } // search finished, update title and status message else _finishSearch(resultCount); }; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; /** * Default splitQuery function. Can be overridden in ``sphinx.search`` with a @@ -152,13 +176,26 @@ const Search = { _queued_query: null, _pulse_status: -1, - htmlToText: (htmlString) => { + htmlToText: (htmlString, anchor) => { const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; + if (docContent) return docContent.textContent; + console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." ); return ""; }, @@ -231,16 +268,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -276,21 +304,38 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - // array of [docname, title, anchor, descr, score, filename] - let results = []; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + _removeChildren(document.getElementById("search-progress")); - const queryLower = query.toLowerCase(); + const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, id !== null ? "#" + id : "", null, - score, + score + boost, filenames[file], ]); } @@ -300,46 +345,47 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ docNames[file], titles[file], id ? "#" + id : "", null, score, filenames[file], - ]); + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } } } } // lookup as object objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)) ); // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); - - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; - }); + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; // remove duplicate search results // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept @@ -353,14 +399,19 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging //Search.lastresults = results.slice(); // a copy // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms); }, /** @@ -458,14 +509,18 @@ const Search = { // add support for partial matches if (word.length > 2) { const escapedWord = _escapeRegExp(word); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); - }); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } } // no match but word was a required one @@ -488,9 +543,8 @@ const Search = { // create the mapping files.forEach((file) => { - if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [word]); + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); }); }); @@ -541,8 +595,8 @@ const Search = { * search summary for a given text. keywords is a list * of stemmed words. */ - makeSearchSummary: (htmlText, keywords) => { - const text = Search.htmlToText(htmlText); + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); if (text === "") return null; const textLower = text.toLowerCase(); diff --git a/apps/_documentation/static/pt/_static/translations.js b/apps/_documentation/static/pt/_static/translations.js index b1ae839f6..92521cc73 100644 --- a/apps/_documentation/static/pt/_static/translations.js +++ b/apps/_documentation/static/pt/_static/translations.js @@ -2,8 +2,7 @@ Documentation.addTranslations({ "locale": "pt", "messages": { "%(filename)s — %(docstitle)s": "", - "© Copyright %(copyright)s.": "", - "© Copyright %(copyright)s.": "", + "© %(copyright_prefix)s %(copyright)s.": "", ", in ": "", "About these documents": "", "Automatically generated list of changes in version %(version)s": "", @@ -21,7 +20,7 @@ Documentation.addTranslations({ "Go": "", "Hide Search Matches": "", "Index": "", - "Index – %(key)s": "", + "Index – %(key)s": "", "Index pages by letter": "", "Indices and tables:": "", "Last updated on %(last_updated)s.": "", diff --git a/apps/_documentation/static/pt/chapter-01.html b/apps/_documentation/static/pt/chapter-01.html index 01915e804..dc16e452a 100644 --- a/apps/_documentation/static/pt/chapter-01.html +++ b/apps/_documentation/static/pt/chapter-01.html @@ -1,26 +1,28 @@ - + - + - O que é py4web? — Documentação py4web 1.20230507.1 - - - - + O que é py4web? — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -60,7 +62,7 @@
                • Ajuda, recursos e dicas
                • Instalação e colocação em funcionamento
                • O Dashboard
                • -
                • Criando seu primeiro aplicativo
                • +
                • Creating an app
                • Fixures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -99,7 +101,7 @@
                  -

                  O que é py4web?

                  +

                  O que é py4web?

                  PY4WEB is a web framework for rapid development of efficient database driven web applications. It is an evolution of the popular web2py framework, but much faster and slicker. Its internal design has been much @@ -173,7 +175,7 @@

                  O que é py4web? -

                  Acknowledgments

                  +

                  Acknowledgments

                  Many thanks to everyone who has contributed to the project, and especially:

                  Special thanks to Sam de Alfaro, who designed the official logo of py4web. We friendly call the logo «Axel the axolotl»: it magically represents the sense of kindness and inclusion. We believe it’s the cornerstone of our growing community.

                  _images/logo.png @@ -206,7 +208,7 @@

                  Acknowledgments -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Compilado com Sphinx usando um @@ -231,7 +233,7 @@

                  Acknowledgments - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/pt/chapter-02.html b/apps/_documentation/static/pt/chapter-02.html index ab79d75af..56bb40074 100644 --- a/apps/_documentation/static/pt/chapter-02.html +++ b/apps/_documentation/static/pt/chapter-02.html @@ -1,26 +1,28 @@ - + - + - Ajuda, recursos e dicas — Documentação py4web 1.20230507.1 - - - - + Ajuda, recursos e dicas — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -75,7 +77,7 @@
                • Instalação e colocação em funcionamento
                • O Dashboard
                • -
                • Criando seu primeiro aplicativo
                • +
                • Creating an app
                • Fixures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -114,55 +116,64 @@
                  -

                  Ajuda, recursos e dicas

                  +

                  Ajuda, recursos e dicas

                  Nós fizemos o nosso melhor para tornar simples PY4WEB e limpo. Mas você sabe, moderno programação web é uma tarefa difícil. Ela exige uma mente aberta, capaz de saltar com frequência (sem ser perdida!) De python para HTML para javascript para css e gestão de banco de dados mesmo. Mas não tenha medo, neste manual vamos ajudá-lo lado a lado nesta jornada. E há muitos outros recursos valiosos que nós vamos mostrar-lhe.

                  -

                  Recursos

                  +

                  Recursos

                  -

                  Este manual

                  -

                  This manual is the Reference Manual for py4web. It’s available online at https://py4web.com/_documentation/static/index.html, where you’ll also find the PDF and EBOOK version, in multiple languages. It written in RestructuredText and generated using Sphinx.

                  +

                  Este manual

                  +

                  This manual is the Reference Manual for py4web. It’s available online at https://py4web.com/_documentation/static/index.html, where you’ll also find the +PDF and EBOOK version, in multiple languages. It written in RestructuredText and generated using Sphinx.

                  -

                  O grupo Google

                  +

                  O grupo Google

                  Existe uma lista de discussão dedicado hospedado no Google Groups, consulte https://groups.google.com/g/py4web. Esta é a principal fonte de discussões para desenvolvedores e usuários simples. Para qualquer problema que você deve enfrentar, este é o lugar certo para procurar uma dica ou uma solução.

                  -

                  The Discord server

                  -

                  For quick questions and chats you can also use the free Discord server dedicated to py4web. You could usually find many py4web developers hanging out in the channel.

                  +

                  The Discord server

                  +

                  For quick questions and chats you can also use the free Discord server dedicated to py4web. You could usually find +many py4web developers hanging out in the channel.

                  -

                  Tutoriais e vídeo

                  +

                  Tutoriais e vídeo

                  There are many tutorials and videos available. Here are some of them:

                  -

                  As fontes no GitHub

                  -

                  Last but not least, py4web is Open Source, with a BSD v3 license, hosted on GitHub at https://github.com/web2py/py4web. This means that you can read, study and experiment -with all of its internal details by yourself.

                  +

                  As fontes no GitHub

                  +

                  Last but not least, py4web is Open Source, with a BSD v3 license, hosted on GitHub at https://github.com/web2py/py4web. This means that you can read, +study and experiment with all of its internal details by yourself.

                  -

                  Dicas e sugestões

                  +

                  Dicas e sugestões

                  Este parágrafo é dedicado a dicas preliminares, sugestões e dicas que podem ser úteis para saber antes de começar a aprender py4web.

                  -

                  Pré-requisitos

                  +

                  Pré-requisitos

                  A fim de compreender py4web você precisa de pelo menos um conhecimento básico python. Há muitos livros, cursos e tutoriais disponíveis na Web - escolher o que é melhor para você. decoradores do Python, em particular, são um marco de qualquer quadro python web e você tem que compreendê-lo totalmente.

                  -

                  Um local de trabalho python moderna

                  -

                  In the following chapters you will start coding on your computer. We suggest you to setup a modern python workplace if you plan to do it efficiently and safely. -Even for running simple examples and experimenting a little, we strongly suggest to use an Integrated Development Environment (IDE). This will make your programming experience much better, allowing syntax checking, linting and visual debugging. +

                  Um local de trabalho python moderna

                  +

                  In the following chapters you will start coding on your computer. We suggest you to setup a modern python workplace if you plan to do it efficiently +and safely. Even for running simple examples and experimenting a little, we strongly suggest to use an Integrated Development Environment (IDE). +This will make your programming experience much better, allowing syntax checking, linting and visual debugging. Nowadays there are two free and multi-platform main choices: Microsoft Visual Studio Code aka VScode and JetBrains PyCharm.

                  Quando você vai começar a lidar com programas mais complexos e confiabilidade necessidade, sugerimos também para:

                    -
                  • usar ambientes virtuais (também chamado ** virtualenv **, veja aqui <https://docs.python.org/3.7/tutorial/venv.html> __ para uma introdução). Em um ambiente de trabalho complexo isso vai evitar a ser confuso com outros programas Python e módulos

                  • +
                  • use virtual environments (also called virtualenv, see +here for an +introduction). In a complex workplace this will avoid to be messed up +with other python programs and modules

                  • use git to keep track of your program’s changes and save your changes in a safe place online (GitHub, GitLat, or Bitbucket).

                  • use an editor with Syntax Highlighting. We highly recommend @@ -170,20 +181,21 @@

                    Um local de trabalho python moderna -

                    Depuração py4web com VScode

                    +

                    Depuração py4web com VScode

                    It’s quite simple to run and debug py4web within VScode.

                    If you have installed py4web from source, you just need to open the main py4web folder (not the apps folder!) with VScode and add:

                    "args": ["run", "apps"],
                     "program": "your_full_path_to_py4web.py",
                     
                    -

                    to the vscode launch.json configuration file. Note that if you’re using Windows the «your_full_path_to_py4web.py» parameter must be written using forward slash only, like +

                    to the vscode launch.json configuration file. Note that if you’re using Windows the «your_full_path_to_py4web.py» parameter must be written using +forward slash only, like «C:/Users/your_name/py4web/py4web.py».

                    If you have instead installed py4web from pip, you need to:

                    • open the apps folder with VScode

                    • -
                    • copy the standard py4web.py launcher inside it, but rename it to py4web-start.py in order to avoid import -errors later:

                    • +
                    • copy the standard py4web.py launcher inside it, but rename it to py4web-start.py in +order to avoid import errors later:

                    #!/usr/bin/env python3
                     from py4web.core import cli
                    @@ -203,18 +215,19 @@ 

                    Depuração py4web com VScode -

                    Depuração py4web com PyCharm

                    +

                    Depuração py4web com PyCharm

                    In PyCharm, if you should get gevent errors you need to enable Settings | Build, Execution, Deployment | Python Debugger | Gevent compatible.

                  -

                  Como contribuir

                  -

                  We need help from everyone: support our efforts! You can just participate in the Google group trying to answer other’s questions, submit bugs using or create pull requests on the GitHub -repository.

                  +

                  Como contribuir

                  +

                  We need help from everyone: support our efforts! You can just participate in the Google group trying to answer other’s questions, submit bugs using or +create pull requests on the GitHub repository.

                  Se você deseja corrigir e ampliar este manual, ou mesmo traduzi-lo em uma nova língua estrangeira, você pode ler todas as informações necessárias diretamente no `README específica <https://github.com/web2py/py4web/blob/master/ docs / README.md> `__ no GitHub.

                  It’s really simple! Just change the .RST files in the /doc folder and create a Pull Request on the GitHub repository at https://github.com/web2py/py4web - you can even do it within your browser. -Once the PR is accepted, your changes will be written on the master branch, and will be reflected on the web pages / pdf / epub at the next output generation on the branch.

                  +Once the PR is accepted, your changes will be written on the master branch, and will be reflected on the web pages / pdf / epub at the next output +generation on the branch.

                  @@ -229,7 +242,7 @@

                  Como contribuir -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Compilado com Sphinx usando um @@ -254,7 +267,7 @@

                  Como contribuir - v: 1.20230507.1 + v: 20240915

              • O Dashboard
              • -
              • Criando seu primeiro aplicativo
              • +
              • Creating an app
              • Fixures
              • The Database Abstraction Layer (DAL)
              • The RestAPI
              • @@ -130,15 +132,15 @@
                -

                Instalação e colocação em funcionamento

                +

                Instalação e colocação em funcionamento

                -

                Understanding the design

                +

                Understanding the design

                Before everything else it is important to understand that unlike other web frameworks, is not only a python module that can be imported by apps. It is also a program that is in charge of starting some apps. For this reason you need two things:

                  -
                • the py4web module (which you download from our web site, from pypi, from github)

                • -
                • one or more folders containing collections of apps you want to run.

                • +
                • The py4web module (which you download from our web site, from pypi or from github)

                • +
                • One or more folders containing collections of apps you want to run.

                py4web has command line options to create a folder with some example apps, to initialize an existing folder, and to add scaffolding apps to that folder. @@ -147,29 +149,49 @@

                Understanding the design -

                Plataformas e pré-requisitos suportados

                -

                PY4WEB runs fine on Windows, MacOS and Linux. Its only prerequisite is +

                Plataformas e pré-requisitos suportados

                +

                py4web runs fine on Windows, MacOS and Linux. Its only prerequisite is Python 3.7+, which must be installed in advance (except if you use binaries).

                -

                Procedimentos de configuração

                -

                Existem quatro formas alternativas de correr py4web, com diferentes níveis de dificuldade e flexibilidade. Vamos olhar os prós e contras.

                -
                -

                Instalando a partir de binários

                -

                Esta não é uma instalação real, porque você acabou de copiar um monte de arquivos em seu sistema sem modificá-lo de qualquer maneira. Daí esta é a solução mais simples, especialmente para iniciantes ou alunos, porque ele não requer Python pré-instalado em seu sistema, nem direitos administrativos. Por outro lado, é experimental, poderia conter uma liberação py4web de idade e é muito difícil para adicionar outras funcionalidades a ele.

                -

                A fim de usá-lo você só precisa fazer o download do arquivo mais recente do Windows ou MacOS zip do este repositório externo <https://github.com/nicozanf/py4web-pyinstaller> __. Descompacte-o em uma pasta local e abrir uma linha de comando lá. finalmente executar

                -
                py4web-start set_password
                -py4web-start run apps
                +

                Procedimentos de configuração

                +

                There are four alternative ways of installing py4web, we will guide +you through each of them and if you get stuck, reach +out to us.

                +
                +

                Installing from pip, using a virtual environment

                +

                A instalação completa de qualquer aplicação python complexo como py4web certamente irá modificar o ambiente python do seu sistema. A fim de evitar qualquer alteração indesejada, é um bom hábito de usar um ambiente virtual python (também chamado ** virtualenv **, veja aqui <https://docs.python.org/3.7/tutorial/venv.html> __ para uma introdução). Este é um recurso padrão do Python; se você ainda não sabe virtualenv é um bom momento para começar a sua descoberta!

                +

                Here are the instructions for creating the virtual environment, activating it, +and installing py4web in it:

                +
                +
                python3 -m venv venv
                +. venv/bin/activate
                +python -m pip install --upgrade py4web --no-cache-dir
                +python py4web setup apps
                +python py4web set_password
                +python py4web run apps
                 
                -

                Com este tipo de instalação, lembre-se de usar sempre py4web-start ** ** em vez de ‘py4web’ ou ‘py4web.py’ na seguinte documentação.

                -

                Notice the binaries many not correspond to the latest master -or the latest stable branch of py4web although we do our best to -keep them up to date.

                +

                Starting py4web is same with or without a virtual environment +python py4web run apps

                +
                -
                -

                Instalando a partir de pip

                -

                Using pip is the standard installation procedure for py4web, since it will +

                +

                Installing from pip, without virtual environment

                +

                pip is the basic installation procedure for py4web, it will quickly install the latest stable release of py4web.

                From the command line

                python3 -m pip install --upgrade py4web --no-cache-dir --user
                @@ -182,22 +204,18 @@ 

                Instalando a partir de pip

                -

                Se o py4web comando não é aceito, isso significa que ele não está no caminho do sistema. No Windows, um arquivo py4web.exe especial (apontando para py4web.py) será criado por * pip * no caminho do sistema, mas não se você digitar o * -user * opção por engano.

                -
                -
                -

                Installing using a virtual environment

                -

                A instalação completa de qualquer aplicação python complexo como py4web certamente irá modificar o ambiente python do seu sistema. A fim de evitar qualquer alteração indesejada, é um bom hábito de usar um ambiente virtual python (também chamado ** virtualenv **, veja aqui <https://docs.python.org/3.7/tutorial/venv.html> __ para uma introdução). Este é um recurso padrão do Python; se você ainda não sabe virtualenv é um bom momento para começar a sua descoberta!

                -

                Here are the instructions for creating the virtual environment, activating it, -and installing py4web in it:

                -
                python3 -m venv venv
                -. venv/bin/activate
                -python -m pip install --upgrade py4web --no-cache-dir
                +

                If the command py4web is not accepted, it means it’s not in the system’s +path. On Windows, a special py4web.exe file (pointing to py4web.py) will +be created by pip on the system’s path, but not if you type the +–user option by mistake, then you can run the needed commands like this

                +
                python3 py4web.py setup apps
                +python3 py4web.py set_password
                +python3 py4web.py run apps
                 
                -

                The instructions for starting and running py4web are the same with or without a virtual environment.

                -

                Instalação de fonte (globalmente)

                +

                Instalação de fonte (globalmente)

                This is the traditional way for installing a program, but it works only on Linux and MacOS (Windows does not normally support the make utility). All the requirements will be installed on the @@ -208,6 +226,8 @@

                Instalação de fonte (globalmente) -

                Instalando a partir de fonte (localmente)

                +

                Instalando a partir de fonte (localmente)

                In this way all the requirements will be installed or upgraded on the system’s path, but py4web itself will only be copied on a local folder. This is especially useful if you already have a @@ -233,13 +253,15 @@

                Instalando a partir de fonte (localmente) -
                ./py4web.py setup apps
                +

                -

                Melhoramento

                +

                Melhoramento

                Se você instalou py4web de pip você pode simples atualizá-lo com

                python3 -m pip install --upgrade py4web
                 
                @@ -260,7 +300,7 @@

                Melhoramento

                Aviso

                Isto não irá atualizar automaticamente os aplicativos padrão, como o Dashboard ** ** e padrão ** **. Você tem que remover manualmente esses aplicativos e execute

                -
                py4web setup apps
                +
                py4web setup <path to apps_folder>
                 

                a fim de re-instalá-los. Esta é uma precaução de segurança, no caso de você fez alterações para esses aplicativos.

                @@ -271,11 +311,8 @@

                Melhoramento -

                Primeira corrida

                +

                Primeira corrida

                Correndo py4web utilizando qualquer um procedimento anterior deve produzir uma saída como esta:

                -
                py4web run apps
                -
                -
                _images/first_run.png

                Generally apps is the name of the folder where you keep all your apps, and can be explicitly set wit the run command. @@ -290,7 +327,8 @@

                Primeira corridaNota

                Alguns aplicativos - como o Dashboard ** ** e padrão ** ** - têm um papel especial na py4web e, portanto, seus começos nome real com `` _`` para evitar conflitos com aplicativos criados por você.

                -

                Uma vez py4web está sendo executado você pode acessar um aplicativo específico nas seguintes URLs:

                +

                Once py4web is running you can access a specific app at the following +urls from the local machine:

                http://localhost:8000
                 http://localhost:8000/_dashboard
                 http://localhost:8000/{yourappname}/index
                @@ -309,7 +347,7 @@ 

                Primeira corrida -

                Opções de linha de comando

                +

                Opções de linha de comando

                py4web fornece várias opções de linha de comando que podem ser listados por executá-lo sem qualquer argumento

                # py4web
                 
                @@ -317,7 +355,7 @@

                Opções de linha de comando

                Você pode ter ajuda adicional para uma opção de linha de comando específico, executando-o com o ** - ajuda ** ou ** - h ** argumento.

                -

                Opção `` comando call``

                +

                Opção `` comando call``

                # py4web call -h
                 Usage: py4web.py call [OPTIONS] APPS_FOLDER FUNC
                 
                @@ -338,7 +376,7 @@ 

                Opções de linha de comando -

                Opção `` comando new_app``

                +

                Opção `` comando new_app``

                # py4web new_app -h
                 Usage: py4web.py new_app [OPTIONS] APPS_FOLDER APP_NAME
                 
                @@ -355,24 +393,27 @@ 

                Opções de linha de comando -

                Opção `` comando run``

                +

                Opção `` comando run``

                # py4web run -h
                 Usage: py4web.py run [OPTIONS] APPS_FOLDER
                 
                -  Run all the applications on apps_folder
                +  Run the applications on apps_folder
                 
                 Options:
                   -Y, --yes                     No prompt, assume yes to questions
                                                 [default: False]
                 
                -  -H, --host TEXT               Host name  [default: 127.0.0.1]
                +  -H, --host TEXT               Host listening IP [default: 127.0.0.1]
                   -P, --port INTEGER            Port number  [default: 8000]
                +  -A, --app_names TEXT          List of apps to run, comma separated (all if omitted or
                +                                empty)
                   -p, --password_file TEXT      File for the encrypted password  [default:
                                                 password.txt]
                -
                -  -s, --server [default|wsgiref|tornado|gunicorn|gevent|waitress|
                -                geventWebSocketServer|wsgirefThreadingServer|rocketServer]
                -                                server to use  [default: default]
                +  -Q, --quiet                   Suppress server output
                +  -R, --routes                  Write apps routes to file
                +  -s, --server                  [default|wsgiref|tornado|gunicorn|gevent|waitress|gunicorn|gunicornGevent|gevent|
                +                                geventWebSocketServer|geventWs|wsgirefThreadingServer|wsgiTh|rocketServer]
                +                                Web server to use
                   -w, --number_workers INTEGER  Number of workers  [default: 0]
                   -d, --dashboard_mode TEXT     Dashboard mode: demo, readonly, full, none
                                                 [default: full]
                @@ -389,9 +430,18 @@ 

                Opções de linha de comandoapp_names option lets you filter which specific apps you want to serve (comma separated). If absent or empty +all the apps in the APPS_FOLDER will be run.

                +

                By default (for security reasons) the py4web framework will listen only on 127.0.0.1, i.e. localhost. +If you need to reach it from other machines you must specify the host option, +like py4web run --host 0.0.0.0 apps.

                +

                The url_prefix option is useful for routing at the py4web level. It allows mapping to multiple versions of py4web +running on different ports as long as the url_prefix and port match the location. For example +py4web run --url_prefix=/abracadabra --port 8000 apps.

                By default py4web will automatically reload an application upon any changes to the python files of that application. The reloading will occur on any first incoming request to the application that has been changed (lazy-mode). If you prefer an immediate reloading (sync-mode), use @@ -413,7 +463,7 @@

                Opções de linha de comando -

                Opção `` comando set_password``

                +

                Opção `` comando set_password``

                # py4web set_password -h
                 Usage: py4web.py set_password [OPTIONS]
                 
                @@ -446,7 +496,7 @@ 

                Opções de linha de comando -

                Opção `` comando setup``

                +

                Opção `` comando setup``

                # py4web setup -h
                 Usage: py4web.py setup [OPTIONS] APPS_FOLDER
                 
                @@ -460,7 +510,7 @@ 

                Opções de linha de comando -

                Opção `` comando shell``

                +

                Opção `` comando shell``

                # py4web shell -h
                 Usage: py4web.py shell [OPTIONS] APPS_FOLDER
                 
                @@ -480,7 +530,7 @@ 

                Opções de linha de comando -

                Opção `` comando version``

                +

                Opção `` comando version``

                # py4web version -h
                 Usage: py4web.py version [OPTIONS]
                 
                @@ -496,13 +546,13 @@ 

                Opções de linha de comando -

                Special installations

                +

                Special installations

                There are special cases in which you cannot or don’t want to use one of the generic installation instructions we’ve already described. There is a special folder called deployment_tools in the py4web repository that collects some special recipes. They are briefly described here, along with some tips and tricks.

                -

                HTTPS

                +

                HTTPS

                To use https with the build-in web server (Rocket3) these are the steps:

                -

                WSGI

                +

                WSGI

                py4web is a standard WSGI application. So, if a full program installation it’s not feasible you can simply run py4web as a WSGI app. For example, using gunicorn-cli, create a python file:

                @@ -549,7 +599,7 @@

                WSGI

                The wsgi function takes arguments with the same name as the command line arguments.

                -

                Deployment on GCloud (aka GAE - Google App Engine)

                +

                Deployment on GCloud (aka GAE - Google App Engine)

                Login into the Gcloud console and create a new project. You will obtain a project id that looks like “{project_name}-{number}”.

                @@ -587,21 +637,22 @@

                Deployment on GCloud (aka GAE - Google App Engine)Você pode querer personalizar o Makefile e app.yaml para atender às suas necessidades. Você não deve precisar editar `` main.py``.

                -

                Implantação em PythonAnywhere.com

                +

                Implantação em PythonAnywhere.com

                Watch the YouTube video and follow the detailed tutorial . The bottle_app.py script is in py4web/deployment_tools/pythonanywhere.com/bottle_app.py

                -

                Deployment on Docker/Podman

                +

                Deployment on Docker/Podman

                On deployment_tools/docker there is a simple Dockerfile for quickly running a py4web container. There is also -a docker-compose.yml file for setting up a more complex multi-container with PostgreSQL.

                +a docker-compose.yml file for setting up a more complex multi-container with PostgreSQL. +A ready docker example based on the Scaffold application can be cloned from this repository <https://github.com/macneiln/docker-py4web-scaffold>

                Note that you can use them also with Podman, which has the advantage of does not requiring sudo and does not running any background daemon.

                -

                Deployment on Ubuntu

                +

                Deployment on Ubuntu

                On deployment_tools/ubuntu there is a bash script tested with Ubuntu Server 20.04.03 LTS. It uses nginx and self-signed certificates. It optionally manage iptables, too.

                @@ -619,7 +670,7 @@

                Deployment on Ubuntu
                -

                © Copyright 2020, BSDv3 License.

                +

                © Copyright 2024, BSDv3 License.

                Compilado com
                Sphinx usando um @@ -644,7 +695,7 @@

                Deployment on Ubuntu - v: 1.20230507.1 + v: 20240915

            • -
            • Criando seu primeiro aplicativo
            • +
            • Creating an app
            • Fixures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -100,13 +102,13 @@
              -

              O Dashboard

              +

              O Dashboard

              The Dashboard is the standard web based IDE; you will surely use it extensively to manage the applications and check your databases. Looking at its interface is a good way to start exploring py4web and its components.

              -

              A página Web principal

              +

              A página Web principal

              When you run the standard py4web program, it starts a web server with a main web page listening on http://127.0.0.1:8000 (which means that it is listening on the TCP port 8000 on your local PC, using the HTTP protocol).

              @@ -126,7 +128,7 @@

              A página Web principal

              -

              Sessão no Dashboard

              +

              Sessão no Dashboard

              Pressionando o botão do painel irá transmitir-lhe para o login Dashboard. Aqui você deve inserir a senha que você já setup (veja: ref: option comando set_password). Se você não se lembre da senha, você tem que parar o programa com CTRL-C, configurar um novo e execute o py4web novamente.

              _images/dashboard_login.png

              Depois de inserir a senha do painel direito, será exibido com todas as abas comprimido.

              @@ -157,13 +159,13 @@

              Sessão no Dashboard

              Type

              @@ -1171,7 +1211,7 @@

              Field types and validators -

              modificação da tabela e campo em tempo de execução

              +

              modificação da tabela e campo em tempo de execução

              A maioria dos atributos de campos e tabelas podem ser modificados depois que eles são definidos:

              >>> db.define_table('person', Field('name', default=''), format='%(name)s')
               <Table person (id, name)>
              @@ -1235,7 +1275,7 @@ 

              modificação da tabela e campo em tempo de execuçãoque retorna um tuplo `` (valor, erro) . `` `` Error é None`` se a entrada passa a validação.

              -

              Mais sobre envios

              +

              Mais sobre envios

              Considere o seguinte modelo:

              db.define_table('myfile',
                               Field('image', 'upload', default='path/to/file'))
              @@ -1293,7 +1333,7 @@ 

              Mais sobre envios -

              Migrações

              +

              Migrações

              With our example table definition:

              db.define_table('person')
               
              @@ -1346,7 +1386,7 @@

              Migrações -

              Fixação migrações quebrados

              +

              Fixação migrações quebrados

              Há dois problemas comuns com as migrações e existem formas de recuperar a partir deles.

              Um problema é específico com SQLite. SQLite não impor tipos de coluna e não pode soltar colunas. Isto significa que se você tiver uma coluna do tipo string e você removê-lo, não é realmente removido. Se você adicionar a coluna novamente com um tipo diferente (por exemplo, data e hora) você acaba com uma coluna de data e hora que contém strings (lixo para fins práticos). não py4web não reclamar sobre isso, porque ele não sabe o que está no banco de dados, até que ele tenta recuperar registros e falha.

              Se py4web retorna um erro em alguma função de análise ao selecionar registros, muito provavelmente isso é devido a dados corrompidos em uma coluna por causa da questão acima.

              @@ -1366,7 +1406,7 @@

              Fixação migrações quebrados -

              Migração resumo controle

              +

              Migração resumo controle

              A lógica dos vários argumentos de migração estão resumidos neste pseudo-código:

              if DAL.migrate_enabled and table.migrate:
                  if DAL.fake_migrate_all or table.fake_migrate:
              @@ -1378,9 +1418,9 @@ 

              Migração resumo controle -

              Table methods

              +

              Table methods

              -

              `` Insert``

              +

              `` Insert``

              Dada uma tabela, você pode inserir registros

              >>> db.person.insert(name="Alex")
               1
              @@ -1411,7 +1451,7 @@ 

              `` Insert`` -

              `` Query``, `` Set``, `` Rows``

              +

              `` Query``, `` Set``, `` Rows``

              Vamos considerar novamente a tabela definida (e caiu) anteriormente e inserir três registros:

              -

              `` Update_or_insert``

              +

              `` Update_or_insert``

              Algumas vezes você precisa executar uma inserção somente se não há nenhum registro com os mesmos valores como aqueles que estão sendo inseridos. Isso pode ser feito com

              db.define_table('person',
                               Field('name'),
              @@ -1468,7 +1508,7 @@ 

              `` Update_or_insert`` -

              `` Validate_and_insert``, `` validate_and_update``

              +

              `` Validate_and_insert``, `` validate_and_update``

              A função

              ret = db.mytable.validate_and_insert(field='value')
               
              @@ -1477,7 +1517,14 @@

              `` Validate_and_insert``, `` validate_and_update``
              id = db.mytable.insert(field='value')
               

              -

              exceto que ele chama os validadores para os campos antes de realizar a inserção e fianças se a validação não passa. Se a validação não passa os erros podem ser encontradas em `` ret.errors``. `` Ret.errors`` mantém um mapeamento de valores-chave, onde cada chave é o nome do campo cuja validação falhou, e o valor da chave é o resultado do erro de validação (muito parecido com `` form.errors``). Se passar, o ID do novo registro é em `` ret.id``. Mente que normalmente validação é feita pela lógica de processamento de formulário para essa função é raramente necessária.

              +

              except that it calls the validators for the fields before performing the +insert and bails out if the validation does not pass. If validation does +not pass the errors can be found in ret["errors"]. ret["errors"] holds +a key-value mapping where each key is the field name whose validation +failed, and the value of the key is the result from the validation error +(much like form["errors"]). If it passes, the id of the new record is +in ret["id"]. Mind that normally validation is done by the form +processing logic so this function is rarely needed.

              Similarmente

              -

              exceto que ele chama os validadores para os campos antes de realizar a atualização. Note que ele só funciona se consulta envolve uma única tabela. O número de registros atualizados podem ser encontrados em `` ret.updated`` e erros será no `` ret.errors``.

              +

              except that it calls the validators for the fields before performing the +update. Notice that it only works if query involves a single table. The +number of updated records can be found in ret["updated"] and errors +will be in ret["errors"].

              -

              `` Drop``

              +

              `` Drop``

              Finalmente, você pode soltar tabelas e todos os dados serão perdidos:

              db.person.drop()
               
              -

              Marcação de registros

              +

              Marcação de registros

              Etiquetas permite adicionar ou encontrar propriedades anexadas aos registros em seu banco de dados.

              from py4web import DAL, Field
               from pydal.tools.tags import Tags
               
               db = DAL("sqlite:memory")
               db.define_table("thing", Field("name"))
              -properties = Tags(db.thing)
               id1 = db.thing.insert(name="chair")
               id2 = db.thing.insert(name="table")
              +properties = Tags(db.thing)
               properties.add(id1, "color/red")
               properties.add(id1, "style/modern")
               properties.add(id2, "color/green")
              @@ -1524,19 +1574,19 @@ 

              Marcação de registrosassert len(rows) == 2

              -

              It is internally implemented as a table, which in +

              Tags are hierarchical. Then find([“color”]) would return id1 and id2 +because both records have tags with “color”.

              +

              It is internally implemented with the creation of an additional table, which in this example would be db.thing_tags_default, because no tail was -specified on the Tags(table, tail=“default”) constructor.

              -

              The find method is doing a search by startswith of the -parameter. Then find([“color”]) would return id1 and id2 -because both records have tags starting with “color”. py4web uses tags as a -flexible mechanism to manage permissions.

              +specified on the Tags(table, tail=“default”) constructor.

              +

              py4web uses Tags as a flexible mechanism to manage permissions, we’ll see +all the details later on the Authorization using Tags chapter.

              -

              Raw SQL

              +

              Raw SQL

              -

              `` executesql``

              +

              `` executesql``

              A DAL permite emitir explicitamente instruções SQL.

              >>> db.executesql('SELECT * FROM person;')
               [(1, u'Massimo'), (2, u'Massimo')]
              @@ -1563,7 +1613,7 @@ 

              `` executesql`` -

              `` _Lastsql``

              +

              `` _Lastsql``

              Se SQL foi executado manualmente usando ExecuteSQL ou foi SQL gerado pelo DAL, você sempre pode encontrar o código SQL em `` db._lastsql``. Isso é útil para fins de depuração:

              >>> rows = db().select(db.person.ALL)
               >>> db._lastsql
              @@ -1576,14 +1626,14 @@ 

              `` _Lastsql`` -

              Temporização de consultas

              +

              Temporização de consultas

              All queries are automatically timed by py4web. The variable db._timings is a list of tuples. Each tuple contains the raw SQL query as passed to the database driver and the time it took to execute in seconds.

              -

              Índices

              +

              Índices

              Atualmente, a API DAL não fornece um comando para criar índices em tabelas, mas isso pode ser feito usando o comando `` executesql``. Isso ocorre porque a existência de índices pode fazer migrações complexa, e é melhor para lidar com eles de forma explícita. Os índices podem ser necessários para esses campos que são usados ​​em consultas recorrentes.

              Aqui está um exemplo de como:

              db = DAL('sqlite://storage.sqlite')
              @@ -1594,7 +1644,7 @@ 

              Índices -

              Generating raw SQL

              +

              Generating raw SQL

              Às vezes você precisa para gerar o SQL, mas não executá-lo. Isso é fácil de fazer com py4web uma vez que cada comando que executa banco de dados IO tem um comando equivalente que não, e simplesmente retorna o SQL que teriam sido executados. Estes comandos têm os mesmos nomes e sintaxe como os funcionais, mas eles começam com um sublinhado:

              Aqui é `` _insert``

              >>> print(db.person._insert(name='Alex'))
              @@ -1627,7 +1677,7 @@ 

              Generating raw SQL -

              `` Comando SELECT``

              +

              `` Comando SELECT``

              Dado um conjunto, `` s``, você pode buscar os registros com o comando `` SELECT``:

              >>> rows = s.select()
               
              @@ -1717,7 +1767,7 @@

              `` Comando SELECT`` -

              Usando um seleto para uso de memória inferior à base de iterador

              +

              Usando um seleto para uso de memória inferior à base de iterador

              Python “iterators” são um tipo de “preguiçoso-avaliação”. Eles dados ‘alimentar’ um passo de tempo; laços tradicionais Python criar todo o conjunto de dados na memória antes de looping.

              O uso tradicional de selecionar é:

              for row in db(db.table).select():
              @@ -1732,7 +1782,7 @@ 

              Usando um seleto para uso de memória inferior à base de iterador -

              Renderizando Rows com represent

              +

              Renderizando Rows com represent

              Você pode querer reescrever linhas retornadas por seleção para tirar proveito de informações de formatação contida na representa a criação dos campos.

              rows = db(query).select()
               repr_row = rows.render(0)
              @@ -1756,7 +1806,7 @@ 

              Renderizando Rows com represent -

              Atalhos

              +

              Atalhos

              A DAL suporta vários atalhos-simplificando código. Em particular:

              -

              Recursivas `` s SELECT``

              +

              Recursivas `` s SELECT``

              Considere a pessoa tabela anterior e uma nova tabela “coisa” fazendo referência a uma “pessoa”:

              -

              `` Orderby``, `` groupby``, `` limitby``, `` distinct``, `` having``, `` orderby_on_limitby``, `` join``, `` left``, `` cache``

              +

              `` Orderby``, `` groupby``, `` limitby``, `` distinct``, `` having``, `` orderby_on_limitby``, `` join``, `` left``, `` cache``

              O comando `` SELECT`` leva uma série de argumentos opcionais.

              -

              ordenar por

              +

              ordenar por

              Você pode buscar os registros classificados pelo nome:

              >>> for row in db().select(db.person.ALL, orderby=db.person.name):
               ...     print(row.name)
              @@ -1891,7 +1941,7 @@ 

              ordenar por -

              groupby, tendo

              +

              groupby, tendo

              Usando `` groupby`` juntamente com `` orderby``, você pode agrupar registros com o mesmo valor para o campo especificado (isto é back-end específico, e não é sobre o Google NoSQL):

              >>> for row in db().select(db.person.ALL,
               ...                        orderby=db.person.name,
              @@ -1910,7 +1960,7 @@ 

              groupby, tendo -

              distinto

              +

              distinto

              Com o argumento `` distinta = True``, você pode especificar que você só quer selecionar registros distintos. Isto tem o mesmo efeito que o agrupamento usando todos os campos especificados, exceto que ele não necessita de classificação. Ao usar distinta é importante não para selecionar todos os campos, e em particular para não selecionar o campo “id”, senão todos os registros serão sempre distintas.

              Aqui está um exemplo:

              >>> for row in db().select(db.person.name, distinct=True):
              @@ -1932,7 +1982,7 @@ 

              distinto -

              limitby

              +

              limitby

              Com `` limitby = (min, max) ``, pode seleccionar um subconjunto dos registos de deslocamento = min, mas não incluindo offset = máx. No próximo exemplo nós selecionamos os dois primeiros registros a partir de zero:

              >>> for row in db().select(db.person.ALL, limitby=(0, 2)):
               ...     print(row.name)
              @@ -1943,25 +1993,25 @@ 

              limitby -

              orderby_on_limitby

              +

              orderby_on_limitby

              Note-se que os padrões DAL de adicionar implicitamente um orderby ao usar um limitby. Isso garante a mesma consulta retorna os mesmos resultados de cada vez, importante para a paginação. Mas pode causar problemas de desempenho. use `` orderby_on_limitby = False`` para mudar isso (isso o padrão é True).

              -

              juntar-se, deixou

              +

              juntar-se, deixou

              These are involved in managing One to many relation. They are described in Inner join and Left outer join sections respectively.

              -

              cache, em cache

              +

              cache, em cache

              An example use which gives much faster selects is:

              -
              rows = db(query).select(cache=(cache.ram, 3600), cacheable=True)
              +
              rows = db(query).select(cache=(cache.get, 3600), cacheable=True)
               

              Look at Caching selects, to understand what the trade-offs are.

              -

              Operadores lógicos

              +

              Operadores lógicos

              As consultas podem ser combinados usando o binário operador AND “` & `”:

              >>> rows = db((db.person.name=='Alex') & (db.person.id > 3)).select()
               >>> for row in rows: print row.id, row.name
              @@ -2000,7 +2050,7 @@ 

              Operadores lógicos -

              `` Count``, `` isempty``, `` DELETE``, `` update``

              +

              `` Count``, `` isempty``, `` DELETE``, `` update``

              Você pode contar registros em um conjunto:

              >>> db(db.person.name != 'William').count()
               3
              @@ -2026,7 +2076,7 @@ 

              `` Count``, `` isempty``, `` DELETE``, `` update``O método update` retorna o número de registros que foram atualizados.

              -

              Expressões

              +

              Expressões

              O valor atribuído uma instrução de atualização pode ser uma expressão. Por exemplo, considere este modelo

              db.define_table('person',
                               Field('name'),
              @@ -2046,7 +2096,7 @@ 

              Expressões -

              `` case``

              +

              `` case``

              Uma expressão pode conter uma cláusula case, por exemplo:

              >>> condition = db.person.name.startswith('B')
               >>> yes_or_no = condition.case('Yes', 'No')
              @@ -2060,7 +2110,7 @@ 

              `` case`` -

              `` Update_record``

              +

              `` Update_record``

              py4web também permite actualizar um único registro que já está na memória usando `` update_record``

              >>> row = db(db.person.id == 2).select().first()
               >>> row.update_record(name='Curt')
              @@ -2092,7 +2142,7 @@ 

              `` Update_record`` -

              Inserir e atualizar a partir de um dicionário

              +

              Inserir e atualizar a partir de um dicionário

              Um problema comum é composto de precisar inserir ou atualizar registros em uma tabela onde o nome da tabela, o campo para ser atualizado, eo valor para o campo são armazenados em variáveis. Por exemplo: `` tablename``, `` fieldname``, e `` value``.

              A inserção pode ser feito usando a seguinte sintaxe:

              db[tablename].insert(**{fieldname:value})
              @@ -2105,7 +2155,7 @@ 

              Inserir e atualizar a partir de um dicionário -

              `` `` First`` e last``

              +

              `` `` First`` e last``

              Dado um objecto linhas contendo registos:

              rows = db(query).select()
               first_row = rows.first()
              @@ -2120,7 +2170,7 @@ 

              `` `` First`` e last`` -

              `` `` As_dict`` e as_list``

              +

              `` `` As_dict`` e as_list``

              Fila objecto pode ser serializados em um dicionário normal usando a `` as_dict () `` método e um objecto de linhas pode ser serializados em uma lista de dicionários usando a `` as_list () `` método. aqui estão alguns exemplos:

              rows = db(query).select()
               rows_list = rows.as_list()
              @@ -2137,7 +2187,7 @@ 

              `` `` As_dict`` e as_list``

              -

              Combinando Rows

              +

              Combinando Rows

              Fileiras objectos podem ser combinadas no nível Python. Aqui assumimos:

              >>> print(rows1)
               person.name
              @@ -2178,7 +2228,7 @@ 

              Combinando Rows -

              `` Find``, `` exclude``, `` sort``

              +

              `` Find``, `` exclude``, `` sort``

              Algumas vezes você precisa executar duas seleciona e um contém um subconjunto de um seleto anterior. Neste case, é inútil para acessar o banco de dados novamente. Os `` find``, `` exclude`` e `` objetos sort`` permitem manipular fileiras objeto e gerar outro sem acessar o banco de dados. Mais especificamente: - `` retorna find`` um novo conjunto de linhas filtradas por uma condição e deixa o inalterados originais. - `` retornos exclude`` um novo conjunto de linhas filtrados por uma condição e remove-los das linhas originais. - `` retorna sort`` um novo conjunto de linhas classificadas por uma condição e deixa o inalterados originais.

              Todos estes métodos dar um único argumento, uma função que age em cada linha individual.

              Aqui está um exemplo de uso:

              @@ -2224,11 +2274,16 @@

              `` Find``, `` exclude``, `` sort`` -

              Selects com cache

              -

              O método de seleção também leva um argumento cache`` , cujo padrão é None. Para fins de armazenamento em cache, deve ser definido como um tuplo em que o primeiro elemento é o modelo do cache ( `` cache.ram, `` cache.disk``, etc), e o segundo elemento é o tempo de validade em segundo .

              +

              Selects com cache

              +

              The select method also takes a cache argument, which defaults to +None. For caching purposes, it should be set to a tuple where the first +element is the cache function with signature (key, callback, expiration) +(for example cache.get assuming cache +is an instance of the py4web cache object), and +the second element is the expiration time in seconds.

              No exemplo a seguir, você vê um controlador que armazena em cache um seleto sobre a mesa db.log previamente definido. As buscas reais dados selecionados do banco de dados back-end não mais do que uma vez a cada 60 segundos e armazena o resultado na memória. Se a próxima chamada para este controlador ocorre em menos de 60 segundos desde o último banco de dados IO, ele simplesmente vai buscar os dados anteriores da memória.

              def cache_db_select():
              -    logs = db().select(db.log.ALL, cache=(cache.ram, 60))
              +    logs = db().select(db.log.ALL, cache=(cache.get, 60))
                   return dict(logs=logs)
               
              @@ -2238,15 +2293,15 @@

              Selects com cache
              rows = db(query).select(cache=(cache.ram, 3600), cacheable=True)
              +
              rows = db(query).select(cache=(cache.get, 3600), cacheable=True)
               

              -

              Computed and Virtual fields

              +

              Computed and Virtual fields

              -

              Campos computados

              +

              Campos computados

              Campos DAL podem ter um atributo `` compute``. Esta deve ser uma função (ou lambda) que recebe um objeto Row e retorna um valor para o campo. Quando um novo registo é modificado, incluindo inserções e atualizações, se um valor para o campo não é fornecido, py4web tenta calcular a partir dos outros valores de campo utilizando a função `` compute``. Aqui está um exemplo:

              >>> db.define_table('item',
               ...                 Field('unit_price', 'double'),
              @@ -2272,11 +2327,11 @@ 

              Campos computados -

              Campos virtuais

              +

              Campos virtuais

              Campos virtuais também são computados campos (como na subseção anterior), mas eles diferem daquelas porque são * * virtual no sentido de que não são armazenadas no db e eles são calculados a cada vez registros são extraídos do banco de dados. Eles podem ser usados ​​para simplificar o código do usuário sem usar armazenamento adicional, mas eles não podem ser usados ​​para pesquisa.

              -

              Campos virtuais novo estilo (experimental)

              +

              Campos virtuais novo estilo (experimental)

              py4web fornece uma nova e mais fácil maneira de definir campos virtuais e campos virtuais preguiçosos. Esta seção é marcado experimental porque as APIs ainda podem mudar um pouco do que é descrito aqui.

              Aqui vamos considerar o mesmo exemplo na subseção anterior. Em particular, considere o seguinte modelo:

              db.define_table('item',
              @@ -2324,7 +2379,7 @@ 

              Campos virtuais novo estilo (experimental) -

              Campos virtuais velho antigo

              +

              Campos virtuais velho antigo

              A fim de definir um ou mais virtuais campos, você também pode definir uma classe de contêiner, instanciá-lo e vinculá-lo a uma tabela ou a um seleto. Por exemplo, considere a seguinte tabela:

              db.define_table('item',
                               Field('unit_price', 'double'),
              @@ -2413,9 +2468,9 @@ 

              Campos virtuais velho antigo -

              Joins and Relations

              +

              Joins and Relations

              -

              Um para muitos relação

              +

              Um para muitos relação

              Para ilustrar como implementar um para muitos relação com a DAL, definir outra mesa “coisa” que refere-se à mesa “pessoa” que redefinir aqui:

              >>> db.define_table('person',
               ...                 Field('name'))
              @@ -2472,7 +2527,7 @@ 

              Um para muitos relação

              -

              Inner join

              +

              Inner join

              Outra forma de conseguir um resultado semelhante é usando uma junção, especificamente um INNER JOIN. executa py4web junta-se automaticamente e de forma transparente quando a consulta liga dois ou mais tabelas como no exemplo a seguir:

              >>> rows = db(db.person.id == db.thing.owner_id).select()
               >>> for row in rows:
              @@ -2519,7 +2574,7 @@ 

              Inner join -

              Left outer join

              +

              Left outer join

              Observe que Carl não aparecer na lista acima, porque ele não tem as coisas. Se você pretende selecionar sobre as pessoas (se eles têm coisas ou não) e as suas coisas (se tiver algum), então você precisa para realizar um LEFT OUTER JOIN. Isso é feito usando o argumento de “esquerda” da seleção. Aqui está um exemplo:

              >>> rows = db().select(db.person.ALL, db.thing.ALL,
               ...                    left=db.thing.on(db.person.id == db.thing.owner_id))
              @@ -2540,7 +2595,7 @@ 

              Left outer join -

              Agrupamento e contando

              +

              Agrupamento e contando

              Ao fazer junta-se, às vezes você quer agrupar linhas de acordo com certos critérios e contá-los. Por exemplo, contar o número de coisas pertencentes a cada pessoa. py4web permite isso também. Primeiro, você precisa de um operador de contagem. Em segundo lugar, você quer se juntar a tabela a pessoa com o quadro de coisa pelo proprietário. Terceiro, você quer selecionar todas as linhas (pessoa + coisa), agrupá-los por pessoa, e contá-los enquanto agrupamento:

              >>> count = db.person.id.count()
               >>> for row in db(db.person.id == db.thing.owner_id
              @@ -2555,7 +2610,7 @@ 

              Agrupamento e contandoO método count` do objeto campo tem um argumento` distinct` opcional. Quando ajustado para `` True`` especifica que apenas os valores distintos de campo em questão estão a ser contadas.

              -

              Many to many relation

              +

              Many to many relation

              Nos exemplos anteriores, que permitiram uma coisa para ter um proprietário, mas uma pessoa pode ter muitas coisas. E se barco era propriedade de Alex e Curt? Isso requer uma relação muitos-para-muitos, e é realizada através de uma tabela intermediária que liga uma pessoa a uma coisa através de uma relação de propriedade.

              Aqui está como fazê-lo:

              >>> db.define_table('person',
              @@ -2619,10 +2674,12 @@ 

              Many to many relationCurt

              -

              Uma alternativa mais leve para muitos-para-muitos relações é a marcação, encontram-se um exemplo disso na próxima seção. Marcação de obras, mesmo em backends de banco de dados que não suportam JOINs como o Google App Engine NoSQL.

              +

              A lighter alternative to many-to-many relations is tagging, see the +Authorization using Tags chapter. Tagging works even on database backends +that do not support JOINs like the Google App Engine NoSQL.

              -

              A auto-referência e aliases

              +

              A auto-referência e aliases

              É possível definir tabelas com campos que se referem a si mesmos, aqui está um exemplo:

              db.define_table('person',
                               Field('name'),
              @@ -2688,7 +2745,7 @@ 

              A auto-referência e aliases -

              Outros operadores

              +

              Outros operadores

              py4web tem outros operadores que fornecem uma API para operadores SQL equivalentes de acesso. Vamos definir outra mesa “log” para eventos loja de segurança, sua event_time e gravidade, onde a gravidade é um número inteiro.

              >>> db.define_table('log', Field('event'),
               ...                        Field('event_time', 'datetime'),
              @@ -2708,7 +2765,7 @@ 

              Outros operadores -

              `` Like``, `` ilike``, `` regexp``, `` startswith``, `` endswith``, `` contains``, `` upper``, `` lower``

              +

              `` Like``, `` ilike``, `` regexp``, `` startswith``, `` endswith``, `` contains``, `` upper``, `` lower``

              Campos tem um `` operador like`` que você pode usar para combinar strings:

              >>> for row in db(db.log.event.like('port%')).select():
               ...     print(row.event)
              @@ -2757,7 +2814,7 @@ 

              `` Like``, `` ilike``, `` regexp``, `` startswith``, `` endswith``, `` conta

              -

              `` Year``, `` month``, `` day``, `` hour``, `` minutes``, `` seconds``

              +

              `` Year``, `` month``, `` day``, `` hour``, `` minutes``, `` seconds``

              A data ea data e hora campos têm `` day``, `` month`` e `` métodos year``. Os campos de data e hora e de tempo têm `` hour``, `` `` minutes`` e métodos seconds``. Aqui está um exemplo:

              >>> for row in db(db.log.event_time.year() > 2018).select():
               ...     print(row.event)
              @@ -2769,7 +2826,7 @@ 

              `` Year``, `` month``, `` day``, `` hour``, `` minutes``, `` seconds``

              -

              `` Belongs``

              +

              `` Belongs``

              O operador IN SQL é realizado através do método belongs` que devolve verdadeiro quando o valor do campo pertence ao conjunto especificado (lista ou tuplos):

              >>> for row in db(db.log.severity.belongs((1, 2))).select():
               ...     print(row.event)
              @@ -2807,7 +2864,7 @@ 

              `` Belongs`` -

              `` Sum``, `` avg``, `` min``, `` `` max`` e len``

              +

              `` Sum``, `` avg``, `` min``, `` `` max`` e len``

              Anteriormente, você usou o `` operador count`` para contar registros. Da mesma forma, você pode usar o `` operador sum`` para adicionar (soma) os valores de um campo específico de um grupo de registros. Tal como no case de contagem, o resultado de uma soma é recuperado através do objecto de armazenamento:

              >>> sum = db.log.severity.sum()
               >>> print(db().select(sum).first()[sum])
              @@ -2835,14 +2892,14 @@ 

              `` Sum``, `` avg``, `` min``, `` `` max`` e len``

              -

              Substrings

              +

              Substrings

              Pode-se construir uma expressão para se referir a uma substring. Por exemplo, podemos agrupar as coisas cujo nome começa com os mesmos três personagens e selecione apenas um de cada grupo:

              db(db.thing).select(distinct = db.thing.name[:3])
               
              -

              Os valores por defeito com `` `` coalesce`` e coalesce_zero``

              +

              Os valores por defeito com `` `` coalesce`` e coalesce_zero``

              Há momentos em que você precisa para puxar um valor de banco de dados, mas também precisa de valores padrão se o valor para um registro é definido como NULL. Em SQL existe uma função, `` COALESCE``, para isso. py4web tem um método coalesce` equivalente:

              >>> db.define_table('sysuser', Field('username'), Field('fullname'))
               <Table sysuser (id, username, fullname)>
              @@ -2877,9 +2934,9 @@ 

              Os valores por defeito com `` `` coalesce`` e coalesce_zero`` -

              Exportar e importar dados

              +

              Exportar e importar dados

              -

              CSV (uma tabela de cada vez)

              +

              CSV (uma tabela de cada vez)

              Quando um objeto linhas é convertido para uma string é automaticamente serializado na CSV:

              >>> rows = db(db.person.id == db.thing.owner_id).select()
               >>> print(rows)
              @@ -2929,7 +2986,7 @@ 

              CSV (uma tabela de cada vez) -

              CSV (todas as tabelas ao mesmo tempo)

              +

              CSV (todas as tabelas ao mesmo tempo)

              Em py4web, você pode backup / restaurar um banco de dados inteiro com dois comandos:

              Exportar:

              with open('somefile.csv', 'w', encoding='utf-8', newline='') as dumpfile:
              @@ -2956,7 +3013,7 @@ 

              CSV (todas as tabelas ao mesmo tempo) -

              CSV e sincronização de banco de dados remoto

              +

              CSV e sincronização de banco de dados remoto

              Considere mais uma vez o seguinte modelo:

              db.define_table('person',
                               Field('name'))
              @@ -3038,7 +3095,7 @@ 

              CSV e sincronização de banco de dados remoto -

              HTML e XML (uma tabela de cada vez)

              +

              HTML e XML (uma tabela de cada vez)

              Linhas objetos também têm um método xml` (como ajudantes) que serializa-lo para XML / HTML:

              >>> rows = db(db.person.id == db.thing.owner_id).select()
               >>> print(rows.xml())
              @@ -3073,7 +3130,7 @@ 

              HTML e XML (uma tabela de cada vez)`` TAG`` helper +tags, you can easily do that using the universal TAG XML helper that we’ll see later and the Python syntax *<iterable> allowed in function calls:

              >>> rows = db(db.person).select()
              @@ -3087,9 +3144,15 @@ 

              HTML e XML (uma tabela de cada vez)</result>

              +
              +

              Aviso

              +

              Do not confuse the TAG XML helper used here (see the `` TAG`` +chapter) with the Tags method that will be extensively explained +on the Authorization using Tags chapter.

              +

              -

              Representação de dados

              +

              Representação de dados

              O método Rows.export_to_csv_file` aceita um argumento de palavra-chave chamada` represent`. Quando `` True`` ele usará as colunas função `` represent`` ao exportar os dados, em vez dos dados brutos.

              A função também aceita um argumento de palavra-chave chamada `` colnames`` que deve conter uma lista de nomes de colunas um desejo para exportação. O padrão é todas as colunas.

              Ambos `` export_to_csv_file`` e `` import_from_csv_file`` aceitar argumentos de palavra-chave que contam o analisador CSV o formato para salvar / carregar os arquivos: - `` delimiter``: delimitador para separar valores (padrão “”) - `` quotechar : personagem para usar para citar valores String (default para aspas) - `` quoting: sistema de cotação (padrão `` csv.QUOTE_MINIMAL``)

              @@ -3111,9 +3174,9 @@

              Representação de dados

              -

              Características avançadas

              +

              Características avançadas

              -

              `` Lista: <type> `` e `` contains``

              +

              `` Lista: <type> `` e `` contains``

              py4web fornece os seguintes tipos de campos especiais:

              list:string
               list:integer
              @@ -3179,7 +3242,7 @@ 

              Características avançadas

              -

              Herança de tabela

              +

              Herança de tabela

              É possível criar uma tabela que contém todos os campos de outra tabela. É suficiente para passar a outra tabela no lugar de um campo para `` define_table``. Por exemplo

              >>> db.define_table('person', Field('name'), Field('gender'))
               <Table person (id, name, gender)>
              @@ -3209,7 +3272,7 @@ 

              Herança de tabela -

              `` `` Filter_in`` e filter_out``

              +

              `` `` Filter_in`` e filter_out``

              É possível definir um filtro para cada campo a ser chamada antes de um valor é inserido na banco de dados para esse campo e depois de um valor é recuperado a partir da banco de dados.

              Imagine for example that you want to store a serializable Python data structure in a field in the JSON format. Here is how it could be @@ -3232,7 +3295,7 @@

              Herança de tabelaSQLCustomType, as discussed in Personalizados `` tipos Field``.

              -

              retornos de chamada no registro de inserção, exclusão e atualização

              +

              retornos de chamada no registro de inserção, exclusão e atualização

              PY4WEB fornece um mecanismo para registrar retornos de chamada para ser chamado antes e / ou após a inserção, atualização e exclusão de registros.

              Cada tabela armazena seis listas de chamadas de retorno:

              db.mytable._before_insert
              @@ -3275,7 +3338,7 @@ 

              retornos de chamada no registro de inserção, exclusão e atualizaçãoAlgumas vezes uma chamada de retorno pode precisar executar uma atualização na mesma ou em uma tabela diferente e se quer evitar disparar outras chamadas de retorno, o que poderia causar um loop infinito.

              Para este efeito, há os objetos `` Set`` tem um método update_naive` que funciona como` update` mas ignora antes e depois de retornos de chamada.

              -

              Cascades no banco de dados

              +

              Cascades no banco de dados

              Database schema can define relationships which trigger deletions of related records, known as cascading. The DAL is not informed when a record is deleted due to a cascade. So no *_delete callback will ever @@ -3283,7 +3346,7 @@

              Cascades no banco de dados

              -

              versionamento recorde

              +

              versionamento recorde

              É possível pedir py4web para salvar cada cópia de um registro quando o registro é modificado individualmente. Existem diferentes maneiras de fazer isso e que pode ser feito para todas as tabelas ao mesmo tempo usando a sintaxe:

              auth.enable_record_versioning(db)
               
              @@ -3314,7 +3377,7 @@

              versionamento recorde -

              filtros comuns

              +

              filtros comuns

              Um filtro comum é uma generalização da ideia multi-tenancy acima. Ele fornece uma maneira fácil de evitar a repetição da mesma consulta. Considere, por exemplo, a tabela a seguir:

              db.define_table('blog_post',
                               Field('subject'),
              @@ -3341,7 +3404,7 @@ 

              filtros comuns -

              Personalizados `` tipos Field``

              +

              Personalizados `` tipos Field``

              Além de usar o `` filter_in`` e `` filter_out``, é possível definir novos tipos de campos / personalizados. Por exemplo, suponha que você deseja definir um tipo personalizado para armazenar um endereço IP:

              >>> def ip2int(sv):
               ...     "Convert an IPV4 to an integer."
              @@ -3391,7 +3454,7 @@ 

              filtros comuns -

              Usando DAL sem definir tabelas

              +

              Usando DAL sem definir tabelas

              A DAL pode ser usado a partir de qualquer programa Python simplesmente fazendo isso:

              from pydal import DAL, Field
               db = DAL('sqlite://storage.sqlite', folder='path/to/app/databases')
              @@ -3407,7 +3470,7 @@ 

              Usando DAL sem definir tabelas -

              Transação distribuída

              +

              Transação distribuída

              No momento da escrita deste recurso só é suportado pelo PostgreSQL, MySQL e Firebird, uma vez que expõem API para commits de duas fases.

              @@ -3424,7 +3487,7 @@

              Transação distribuída -

              Copiar dados de um para outro db

              +

              Copiar dados de um para outro db

              Considere a situação em que você estiver usando o seguinte banco de dados:

              db = DAL('sqlite://storage.sqlite')
               
              @@ -3437,9 +3500,9 @@

              Copiar dados de um para outro db -

              Pegadinhas

              +

              Pegadinhas

              -

              Nota sobre novo DAL e adaptadores

              +

              Nota sobre novo DAL e adaptadores

              O código fonte do Banco de Dados Camada de Abstração foi completamente reescrito em 2010. Enquanto ele permanece compatível com versões anteriores, a reescrita tornou mais modular e mais fácil de estender. Aqui nós explicamos a lógica principal.

              The module “dal.py” defines, among other, the following classes.

              ConnectionPool
              @@ -3704,7 +3767,7 @@ 

              Nota sobre novo DAL e adaptadores -

              SQLite

              +

              SQLite

              SQLite does not support dropping and altering columns. That means that py4web migrations will work up to a point. If you delete a field from a table, the column will remain in the database but will be invisible to @@ -3719,7 +3782,7 @@

              SQLiteSQLite não tem um tipo booleano. py4web mapeia internamente booleans para uma strings de 1 carácter, com ‘T’ e ‘F’ representar Verdadeiro e Falso. A DAL lida com isso completamente; a abstração de um verdadeiro valor booleano funciona bem. Mas se você estiver atualizando a tabela SQLite com o SQL diretamente, estar ciente da implementação py4web, e evitar o uso de 0 e 1 valores.

              -

              MySQL

              +

              MySQL

              MySQL does not support multiple ALTER TABLE within a single transaction. This means that any migration process is broken into multiple commits. If something happens that causes a failure it is possible to break a @@ -3732,7 +3795,7 @@

              MySQL again).

              -

              Google SQL

              +

              Google SQL

              Google SQL has the same problems as MySQL and more. In particular table metadata itself must be stored in the database in a table that is not migrated by py4web. This is because Google App Engine has a read-only @@ -3745,7 +3808,7 @@

              Google SQLpy4web_filesystem.

              -

              MSSQL (Microsoft SQL Server)

              +

              MSSQL (Microsoft SQL Server)

              não MSSQL <2012 não suporta o SQL OFFSET palavra-chave. Portanto, o banco de dados não pode fazer a paginação. Ao fazer um `` limitby = (a, b) `` py4web vai buscar a primeira `` a + b`` linhas e descartar o primeiro `` a``. Isto pode resultar numa sobrecarga considerável quando comparado com outros bancos de dados. Se você estiver usando MSSQL> = 2005, o prefixo recomendado para uso é `` mssql3: // `` que fornece um método para evitar o problema de buscar todo o conjunto de resultados não-paginado. Se você estiver em MSSQL> = 2012, use `` mssql4: // `` que usa o `` OFFSET … ROWS … FETCH PRÓXIMO … ROWS ONLY`` construção para apoiar a paginação nativamente, sem sucessos de desempenho como outros backends. O `` mssql: // `` uri também reforça (por razões históricas) o uso de `` colunas text``, que são superseeded em versões mais recentes (a partir de 2005) por `` varchar (max) . `` Mssql3: // `` e `` mssql4: // `` deve ser usado se você não quer enfrentar algumas limitações do - oficialmente obsoleto - `` colunas text.

              MSSQL tem problemas com referências circulares em tabelas que têm onDelete CASCADE. Este é um bug MSSQL e você trabalhar em torno dele, definindo o atributo onDelete para todos os campos de referência a “nenhuma acção”. Você também pode fazê-lo uma vez por todas, antes de definir tabelas:

              db = DAL('mssql://....')
              @@ -3764,11 +3827,11 @@ 

              MSSQL (Microsoft SQL Server) -

              Oráculo

              +

              Oráculo

              A Oracle também não suporta a paginação. Ele não suporta nem a OFFSET nem as palavras-chave limite. PY4WEB alcança a paginação, traduzindo um `` db (…). Select (limitby = (a, b)) `` em um complexo de três vias SELECT aninhada (como sugerido por documentação oficial Oracle). Isso funciona para simples escolha, mas pode quebrar para seleciona complexos envolvendo campos e ou junta alias.

              -

              Google NoSQL (Datastore)

              +

              Google NoSQL (Datastore)

              Google NoSQL (Datastore) não permite que se junta, deixou junta, agregados, expressão ou envolvendo mais de uma tabela, o ‘como’ pesquisas operador em campos “texto”.

              As transações são limitados e não fornecida automaticamente pelo py4web (você precisa usar a API do Google `` run_in_transaction`` que você pode procurar na documentação do Google App Engine online).

              O Google também limita o número de registros que você pode recuperar em cada uma consulta (1000, no momento da escrita). No Google armazenamento de dados IDs de registro são inteiro, mas eles não são seqüenciais. Enquanto em SQL “lista: string” tipo é mapeado em um tipo de “texto”, no Google Datastore é mapeado em um `` ListStringProperty``. Da mesma forma “lista: número inteiro” e “lista: referência” são mapeados para `` ListProperty``. Isso faz buscas por conteúdo dentro desses campos tipos mais eficientes no Google NoSQL que em bancos de dados SQL.

              @@ -3787,7 +3850,7 @@

              Google NoSQL (Datastore)
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Compilado com
              Sphinx usando um @@ -3812,7 +3875,7 @@

              Google NoSQL (Datastore) - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/pt/chapter-08.html b/apps/_documentation/static/pt/chapter-08.html index 2ffa46285..6bcad2e2c 100644 --- a/apps/_documentation/static/pt/chapter-08.html +++ b/apps/_documentation/static/pt/chapter-08.html @@ -1,26 +1,28 @@ - + - + - The RestAPI — Documentação py4web 1.20230507.1 - - - - + The RestAPI — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
              - 1.20230507.1 + 20240915
              @@ -57,7 +59,7 @@
            • Ajuda, recursos e dicas
            • Instalação e colocação em funcionamento
            • O Dashboard
            • -
            • Criando seu primeiro aplicativo
            • +
            • Creating an app
            • Fixures
            • The Database Abstraction Layer (DAL)
            • The RestAPI -

              You will also need a template file templates/form_widgets.html that -contains the following code (as the form_basic.html) :

              -
              <h2 class="title">Form Widget example: Superhero Identity</h2>
              -
              -[[=form]]
              -
              -<h2 class="title">Rows</h2>
              -
              -<ul>
              -[[for row in rows:]]
              -<li>[[=row.id]]: [[=row.superhero]] ([[=row.realname]]) from [[=row.universe]]</li>
              -[[pass]]
              -</ul>
              -
              -

              The result is the same as before, but now we have a radio button widget instead of the dropdown menu!

              -_images/form4.png

              Using widgets in forms is quite easy, and they’ll let you have more control on its pieces.

            • -

              Custom widgets

              +

              Custom widgets

              You can also customize the widgets properties by subclassing the FormStyleDefault class. Let’s have a quick look, improving again our Superhero example:

              -
              #
              -# in form_custom_widgets/__init__.py
              -#
              -import os
              -from py4web import action, Field, DAL
              +
              # in controllers.py
              +from py4web import action, redirect, URL, Field
               from py4web.utils.form import Form, FormStyleDefault, RadioWidget
              -from pydal.validators import IS_NOT_EMPTY, IS_IN_SET
              -from yatl.helpers import INPUT, DIV
              -
              -# database definition
              -DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases')
              -if not os.path.isdir(DB_FOLDER):
              -    os.mkdir(DB_FOLDER)
              -db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER)
              -db.define_table(
              -    'person',
              -    Field('superhero', requires=IS_NOT_EMPTY()),
              -    Field('realname'),
              -    Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])),
              -)
              +from pydal.validators import *
              +from .common import db
               
               # custom widget class definition
               class MyCustomWidget:
              @@ -515,45 +499,28 @@ 

              Custom widgetsreturn control # controllers definition -@action("index", method=["GET", "POST"]) +@action("create_form", method=["GET", "POST"]) @action.uses("form_custom_widgets.html", db) -def index(id=None): +def create_form(): MyStyle = FormStyleDefault MyStyle.classes = FormStyleDefault.classes - MyStyle.widgets['superhero']=MyCustomWidget() - MyStyle.widgets['realname']=MyCustomWidget() - MyStyle.widgets['universe']=RadioWidget() + MyStyle.widgets['name']=MyCustomWidget() + MyStyle.widgets['color']=RadioWidget() - form = Form(db.person, id, deletable=False, formstyle=MyStyle) - rows = db(db.person).select() + form = Form(db.thing, deletable=False, formstyle=MyStyle) + rows = db(db.thing).select() return dict(form=form, rows=rows)

              -

              You will also need a template file templates/form_custom_widgets.html that -contains the following code (as the form_basic.html) :

              -
              <h2 class="title">Form Custom Widgets example: Superhero Identity</h2>
              -
              -[[=form]]
              -
              -<h2 class="title">Rows</h2>
              -
              -<ul>
              -[[for row in rows:]]
              -<li>[[=row.id]]: [[=row.superhero]]  ([[=row.realname]]) from [[=row.universe]] </li>
              -[[pass]]
              -</ul>
              -
              -

              The result is similar to the previous ones, but now we have a custom input field, -with foreground color red and background color black:

              -_images/form5.png +with foreground color red and background color black,

              Even the radio button widget has changed, from red to blue.

              -

              Advanced form design

              +

              Advanced form design

              -

              Form structure manipulation

              +

              Form structure manipulation

              In py4web a form is rendered by YATL helpers. This means the tree structure of a form can be manipulated before the form is serialized in HTML. Here is an example of how to manipulate the generate HTML structure:

              @@ -568,7 +535,7 @@

              Form structure manipulation -

              Custom forms

              +

              Custom forms

              Custom forms allow you to granulary control how the form is processed. In the template file, you can execute specific instructions before the form is displayed or after its data submission by inserting code among the following statements:

              -

              The sidecar parameter

              +

              The sidecar parameter

              The sidecar is the stuff injected in the form along with the submit button.

              For example, you can inject a simple click me button in your form with the following code:

              @@ -622,7 +612,7 @@

              The sidecar parameter

              -

              Validação de formulário

              +

              Validação de formulário

              Validators are classes used to validate input fields (including forms generated from database tables). They are normally assigned using the requires attribute of a table Field object, as already shown on the Construtor Field paragraph of the DAL chapter. Also, you can use advanced validators @@ -711,37 +701,37 @@

              Validação de formulário

              -

              Text format validators

              +

              Text format validators

              -

              IS_ALPHANUMERIC

              +

              IS_ALPHANUMERIC

              This validator checks that a field value contains only characters in the ranges a-z, A-Z, 0-9, and underscores.

              requires = IS_ALPHANUMERIC(error_message='must be alphanumeric!')
               
              -

              IS_LOWER

              +

              IS_LOWER

              This validator never returns an error. It just converts the value to lower case.

              requires = IS_LOWER()
               
              -

              IS_UPPER

              +

              IS_UPPER

              This validator never returns an error. It converts the value to upper case.

              requires = IS_UPPER()
               
              -

              IS_EMAIL

              +

              IS_EMAIL

              It checks that the field value looks like an email address. It does not try to send email to confirm.

              requires = IS_EMAIL(error_message='invalid email!')
               
              -

              IS_MATCH

              +

              IS_MATCH

              This validator matches the value against a regular expression and returns an error if it does not match. Here is an example of usage to validate a US zip code:

              requires = IS_MATCH('^\d{5}(-\d{4})?$',
              @@ -771,7 +761,7 @@ 

              IS_MATCH substring rather than the original value.

              -

              IS_LENGTH

              +

              IS_LENGTH

              Checks if length of field’s value fits between given boundaries. Works for both text and file inputs.

              Its arguments are:

              @@ -800,7 +790,7 @@

              IS_LENGTH

              -

              IS_URL

              +

              IS_URL

              Rejects a URL string if any of the following is true:

              • The string is empty or None

              • @@ -845,8 +835,19 @@

                IS_URL

              +
              +

              IS_SAFE

              +
              requires = IS_SAFE(error_message='Unsafe Content')
              +requires = IS_SAFE(mode="sanitize")
              +requires = IS_SAFE(sanitizer=lambda text: str(XML(text, sanitize=True)))
              +
              +
              +

              This validators is for text fields that should contain HTML and may contain invalid tags (script, ember, object, iframe). +It works by trying to sanitize the content and either provide an error (mode=»error») or replacing the content +with the sanitized one (mode=»sanitize»). You can specify the error message, the mode, and provide your own sanitizer.

              +
              -

              IS_SLUG

              +

              IS_SLUG

              requires = IS_SLUG(maxlen=80, check=False, error_message='must be slug')
               
              @@ -854,7 +855,7 @@

              IS_SLUG<

              If check is set to False (default) it converts the input value to a slug.

              -

              IS_JSON

              +

              IS_JSON

              requires = IS_JSON(error_message='Invalid json', native_json=False)
               
              @@ -863,16 +864,16 @@

              IS_JSON<

              -

              Date and time validators

              +

              Date and time validators

              -

              IS_TIME

              +

              IS_TIME

              This validator checks that a field value contains a valid time in the specified format.

              requires = IS_TIME(error_message='must be HH:MM:SS!')
               
              -

              IS_DATE

              +

              IS_DATE

              This validator checks that a field value contains a valid date in the specified format. It is good practice to specify the format using the translation operator, in order to support different formats in different locales.

              requires = IS_DATE(format=T('%Y-%m-%d'),
                   error_message='must be YYYY-MM-DD!')
              @@ -881,7 +882,7 @@ 

              IS_DATE<

              For the full description on % directives look under the IS_DATETIME validator.

              -

              IS_DATETIME

              +

              IS_DATETIME

              This validator checks that a field value contains a valid datetime in the specified format. It is good practice to specify the format using the translation operator, in order to support different formats in different locales.

              requires = IS_DATETIME(format=T('%Y-%m-%d %H:%M:%S'),
                                  error_message='must be YYYY-MM-DD HH:MM:SS!')
              @@ -903,7 +904,7 @@ 

              IS_DATETIME

              -

              IS_DATE_IN_RANGE

              +

              IS_DATE_IN_RANGE

              Works very much like the previous validator but allows to specify a range:

              requires = IS_DATE_IN_RANGE(format=T('%Y-%m-%d'),
                               minimum=datetime.date(2008, 1, 1),
              @@ -914,7 +915,7 @@ 

              IS_DATE_IN_RANG

              For the full description on % directives look under the IS_DATETIME validator.

              -

              IS_DATETIME_IN_RANGE

              +

              IS_DATETIME_IN_RANGE

              Works very much like the previous validator but allows to specify a range:

              requires = IS_DATETIME_IN_RANGE(format=T('%Y-%m-%d %H:%M:%S'),
                                   minimum=datetime.datetime(2008, 1, 1, 10, 30),
              @@ -926,9 +927,9 @@ 

              IS_DATETIME_IN_

              -

              Range, set and equality validators

              +

              Range, set and equality validators

              -

              IS_EQUAL_TO

              +

              IS_EQUAL_TO

              Checks whether the validated value is equal to a given value (which can be a variable):

              requires = IS_EQUAL_TO(request.vars.password,
                                   error_message='passwords do not match')
              @@ -936,7 +937,7 @@ 

              IS_EQUAL_TO

              -

              IS_NOT_EMPTY

              +

              IS_NOT_EMPTY

              This validator checks that the content of the field value is neither None nor an empty string nor an empty list. A string value is checked for after a .strip().

              requires = IS_NOT_EMPTY(error_message='cannot be empty!')
               
              @@ -947,11 +948,11 @@

              IS_NOT_EMPTY

              -

              IS_NULL_OR

              +

              IS_NULL_OR

              Deprecated, an alias for IS_EMPTY_OR described below.

              -

              IS_EMPTY_OR

              +

              IS_EMPTY_OR

              Sometimes you need to allow empty values on a field along with other requirements. For example a field may be a date but it can also be empty. The IS_EMPTY_OR validator allows this:

              requires = IS_EMPTY_OR(IS_DATE())
              @@ -965,7 +966,7 @@ 

              IS_EMPTY_OR

              -

              IS_EXPR

              +

              IS_EXPR

              This validator let you express a general condition by means of a callable which takes a value to validate and returns the error message or None to accept the input value.

              requires = IS_EXPR(lambda v: T('not divisible by 3') if int(v) % 3 else None)
               
              @@ -983,7 +984,7 @@

              IS_EXPR<

              -

              IS_DECIMAL_IN_RANGE

              +

              IS_DECIMAL_IN_RANGE

              INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10, dot="."))
               
              @@ -995,7 +996,7 @@

              IS_DECIMAL_IN_R

              The dot argument is optional and allows you to internationalize the symbol used to separate the decimals.

              -

              IS_FLOAT_IN_RANGE

              +

              IS_FLOAT_IN_RANGE

              Checks that the field value is a floating point number within a definite range, 0 <= value <= 100 in the following example:

              requires = IS_FLOAT_IN_RANGE(0, 100, dot=".",
                                           error_message='negative or too large!')
              @@ -1004,7 +1005,7 @@ 

              IS_FLOAT_IN_RAN

              The dot argument is optional and allows you to internationalize the symbol used to separate the decimals.

              -

              IS_INT_IN_RANGE

              +

              IS_INT_IN_RANGE

              Checks that the field value is an integer number within a definite range,

              0 <= value < 100 in the following example:

              requires = IS_INT_IN_RANGE(0, 100,
              @@ -1015,7 +1016,7 @@ 

              IS_INT_IN_RANGE

              -

              IS_IN_SET

              +

              IS_IN_SET

              This validator will automatically set the form field to an option field (ie, with a drop-down menu).

              IS_IN_SET checks that the field values are in a set:

              requires = IS_IN_SET(['a', 'b', 'c'], zero=T('choose one'),
              @@ -1033,14 +1034,14 @@ 

              IS_IN_SET

              -

              Checkbox validation

              +

              Checkbox validation

              To force a filled-in form checkbox (such as an acceptance of terms and conditions), use this:

              -
              requires=IS_IN_SET(['on'])
              +
              requires=IS_IN_SET(['ON'])
               
              -

              Dictionaries and tuples with IS_IN_SET

              +

              Dictionaries and tuples with IS_IN_SET

              You may also use a dictionary or a list of tuples to make the drop down list more descriptive:

              # Dictionary example:
               requires = IS_IN_SET({'A':'Apple', 'B':'Banana', 'C':'Cherry'}, zero=None)
              @@ -1051,16 +1052,16 @@ 

              Dictionaries and tuples with IS_IN_SET -

              Sorted options

              +

              Sorted options

              To keep the options alphabetically sorted by their labels into the drop down list, use the sort argument with IS_IN_SET.

              IS_IN_SET([('H', 'Hulk'), ('S', 'Superman'), ('B', 'Batman')], sort=True)
               
              -

              IS_IN_SET and Tagging

              +

              IS_IN_SET and Tagging

              The IS_IN_SET validator has an optional attribute multiple=False. If set to True, multiple values can be stored in one -field. The field should be of type list:integer or list:string as discussed in [[Chapter 6 ../06#list-type-and-contains]]. +field. The field should be of type list:integer or list:string as discussed in `` Lista: <type> `` e `` contains``. An explicit example of tagging is discussed there. We strongly suggest using the jQuery multiselect plugin to render multiple fields.

              Note that when multiple=True, IS_IN_SET will accept zero or more values, i.e. it will accept the field when nothing has been selected. multiple can also be a tuple of the form (a, b) where a and b are the minimum and (exclusive) maximum number of items @@ -1068,9 +1069,9 @@

              IS_IN_SET

              -

              Complexity and security validators

              +

              Complexity and security validators

              -

              IS_STRONG

              +

              IS_STRONG

              Enforces complexity requirements on a field (usually a password field).

              Example:

              requires = IS_STRONG(min=10, special=2, upper=2)
              @@ -1100,7 +1101,7 @@ 

              IS_STRONGnumber = 1, special = 1 which otherwise are all sets to None.

              -

              CRYPT

              +

              CRYPT

              This is also a filter. It performs a secure hash on the input and it is used to prevent passwords from being passed in the clear to the database.

              requires = CRYPT()
               
              @@ -1132,9 +1133,9 @@

              CRYPT

              -

              Special type validators

              +

              Special type validators

              -

              IS_LIST_OF

              +

              IS_LIST_OF

              This validator helps you to ensure length limits on values of type list, for this purpose use its minimum, maximum, and error_message arguments, for example:

              requires = IS_LIST_OF(minimum=2)
              @@ -1153,7 +1154,7 @@ 

              IS_LIST_OF

              -

              IS_LIST_OF_EMAILS

              +

              IS_LIST_OF_EMAILS

              This validator is specifically designed to work with the following field:

              Field('emails', 'list:string',
                     widget=SQLFORM.widgets.text.widget,
              @@ -1179,7 +1180,7 @@ 

              IS_LIST_OF_EMAI

              The effect of the represent argument (at lines 6 and 7) is to add a «mailto:…» link to each email address when the record is rendered in HTML pages.

              -

              ANY_OF

              +

              ANY_OF

              This validator takes a list of validators and accepts a value if any of the validators in the list does (i.e. it acts like a logical OR with respect to given validators).

              requires = ANY_OF([IS_ALPHANUMERIC(), IS_EMAIL()])
              @@ -1196,7 +1197,7 @@ 

              ANY_OF

              -

              IS_IMAGE

              +

              IS_IMAGE

              This validator checks if a file uploaded through the file input was saved in one of the selected image formats and has dimensions (width and height) within given limits.

              It does not check for maximum file size (use IS_LENGTH for that). It returns @@ -1231,7 +1232,7 @@

              IS_IMAGE

              -

              IS_FILE

              +

              IS_FILE

              Checks if name and extension of file uploaded through file input matches given criteria.

              Does not ensure the file type in any way. Returns validation failure if no data was uploaded.

              Its arguments are:

              @@ -1271,7 +1272,7 @@

              IS_FILE<

              -

              IS_UPLOAD_FILENAME

              +

              IS_UPLOAD_FILENAME

              This is the older implementation for checking files, included for backwards compatibility. For new applications, use IS_FILE().

              This validator checks if the name and extension of a file uploaded through the file input matches the given criteria.

              It does not ensure the file type in any way. Returns validation failure @@ -1300,7 +1301,7 @@

              IS_UPLOAD_FILEN

              -

              IS_IPV4

              +

              IS_IPV4

              This validator checks if a field’s value is an IP version 4 address in decimal form. Can be set to force addresses from a certain range.

              IPv4 regex taken from regexlib. The signature for the IS_IPV4 constructor is the following:

              @@ -1348,7 +1349,7 @@

              IS_IPV4<

              -

              IS_IPV6

              +

              IS_IPV6

              This validator checks if a field’s value is an IP version 6 address.

              The signature for the IS_IPV6 constructor is the following:

              IS_IPV6(is_private=None,
              @@ -1395,7 +1396,7 @@ 

              IS_IPV6<

              -

              IS_IPADDRESS

              +

              IS_IPADDRESS

              This validator checks if a field’s value is an IP address (either version 4 or version 6). Can be set to force addresses from within a specific range. Checks are done using the appropriate IS_IPV4 or IS_IPV6 validator.

              @@ -1427,9 +1428,9 @@

              IS_IPADDRESS

              -

              Other validators

              +

              Other validators

              -

              CLEANUP

              +

              CLEANUP

              This is a filter. It never fails. By default it just removes all characters whose decimal ASCII codes are not in the list [10, 13, 32-127]. It always perform an initial strip (i.e. heading and trailing blank characters removal) on the value.

              requires = CLEANUP()
              @@ -1443,9 +1444,9 @@ 

              CLEANUP<

              -

              Database validators

              +

              Database validators

              -

              IS_NOT_IN_DB

              +

              IS_NOT_IN_DB

              Synopsis: IS_NOT_IN_DB(db|set, 'table.field')

              Consider the following example:

              @@ -1481,7 +1482,7 @@

              IS_NOT_IN_DB

              -

              IS_IN_DB

              +

              IS_IN_DB

              Synopsis: IS_IN_DB(db|set, 'table.value_field', '%(representing_field)s', zero='choose one') where the third and fourth arguments are optional.

              @@ -1540,7 +1541,7 @@

              IS_IN_DB

              -

              IS_IN_DB and Tagging

              +

              IS_IN_DB and Tagging

              The IS_IN_DB validator has an optional attribute multiple=False. If set to True multiple values can be stored in one field. This field should be of type list:reference as discussed in `` Lista: <type> `` e `` contains``. An explicit example of tagging is discussed there. Multiple references are handled automatically in create and update forms, but they are transparent to @@ -1548,7 +1549,7 @@

              IS_IN_DB

              -

              Validation functions

              +

              Validation functions

              In order to explicitly define a validation function, we pass to the validation parameter a function that takes the form and returns a dictionary, mapping field names to errors. If the dictionary is non-empty, the errors will be @@ -1558,20 +1559,15 @@

              Validation functionsfrom py4web.utils.form import Form, FormStyleBulma from pydal.validators import IS_INT_IN_RANGE -def check_nonnegative_quantity(form): - if not form.errors and form.vars['product_quantity'] % 2: - form.errors['product_quantity'] = T('The product quantity must be even') +def custom_check(form): + if not 'name' in form.errors and len(form.vars['name']) < 4 + form.errors['name'] = T("too short") @action('form_example', method=['GET', 'POST']) @action.uses('form_example.html', session) def form_example(): - form = Form([ - Field('product_name'), - Field('product_quantity', 'integer', requires=IS_INT_IN_RANGE(0,100))], - validation=check_nonnegative_quantity, - formstyle=FormStyleBulma) + form = Form(db.thing, validation=custom_check) if form.accepted: - # Do something with form.vars['product_name'] and form.vars['product_quantity'] redirect(URL('index')) return dict(form=form) @@ -1591,7 +1587,7 @@

              Validation functions
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Compilado com
              Sphinx usando um @@ -1616,7 +1612,7 @@

              Validation functions - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/pt/chapter-13.html b/apps/_documentation/static/pt/chapter-13.html index 0969a2517..707598b4d 100644 --- a/apps/_documentation/static/pt/chapter-13.html +++ b/apps/_documentation/static/pt/chapter-13.html @@ -1,26 +1,28 @@ - + - + - Authentication and authorization — Documentação py4web 1.20230507.1 - - - - + Authentication and authorization — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
              - 1.20230507.1 + 20240915
              @@ -57,7 +59,7 @@
            • Ajuda, recursos e dicas
            • Instalação e colocação em funcionamento
            • O Dashboard
            • -
            • Criando seu primeiro aplicativo
            • +
            • Creating an app
            • Fixures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -72,6 +74,7 @@
            • Two Factor Authentication
            • @@ -88,6 +91,7 @@
            • Authorization using Tags
            • @@ -122,7 +126,7 @@
              -

              Authentication and authorization

              +

              Authentication and authorization

              Strong authentication and authorization methods are vital for a modern, multiuser web application. While they are often used interchangeably, authentication and authorization @@ -132,7 +136,7 @@

              Authentication and authorization -

              Authentication using Auth

              +

              Authentication using Auth

              py4web comes with a an object Auth and a system of plugins for user authentication. It has the same name as the corresponding web2py one and serves the same purpose but the API and @@ -180,7 +184,7 @@

              Authentication using Auth -

              Interface de autenticação

              +

              Interface de autenticação

              Você pode criar sua própria interface do usuário da web para usuários de login usando as APIs acima, mas py4web fornece um como exemplo, implementada nos seguintes arquivos:

              Aqui `` @ action.uses (auth.user) `` diz py4web que essa ação requer um usuário conectado e deve redirecionar para login se nenhum usuário está logado.

              -

              Two Factor Authentication

              -

              Two factor authentication (or Two-step verification) is a way of improving authentication security. When activated an extra step is added in the login process. In the first step, users are shown the standard username/password form. If they successfully pass this challenge by submitting the correct username and password, and two factor authentication is enabled for the user, the server will present a second form before logging them in.

              +

              Two Factor Authentication

              +

              Two factor authentication (or Two-step verification) is a way of improving authentication security. When activated an extra step is added in the login +process. In the first step, users are shown the standard username/password form. If they successfully pass this challenge by submitting the correct +username and password, and two factor authentication is enabled for the user, the server will present a second form before logging them in.

              There are a few Auth settings available to control how two factor authentication works.

              -

              The follow can be specified on Auth instantiation:

              +

              The following can be specified on Auth instantiation:

                -
              • two_factor_required

              • -
              • two_factor_send

              • +
              • two_factor_required

              • +
              • two_factor_send

              • +
              • two_factor_validate

              -

              two_factor_required

              -

              When you pass a method name to the two_factor_filter parameter you are telling py4web to call that method to determine whether or not this login should be use or bypass two factor authentication. If your method returns True, then this login requires two factor. If it returns False, two factor authentication is bypassed for this login.

              -

              Sample two_factor_filter method

              +

              two_factor_required

              +

              When you pass a method name to the two_factor_required parameter you are telling py4web to call that method to determine whether or not this login should +be use or bypass two factor authentication. If your method returns True, then this login requires two factor. If it returns False, two factor authentication +is bypassed for this login.

              +

              Sample two_factor_required method

              This example shows how to allow users that are on a specific network.

              def user_outside_network(user, request):
                   import ipaddress
              @@ -277,8 +286,9 @@ 

              two_factor_required -

              two_factor_send

              -

              When two factor authentication is active, py4web generates a 6 digit code (using random.randint) and sends it to you. How this code is sent, is up to you. The two_factor_send argument to the Auth class allows you to specify the method that sends the two factor code to the user.

              +

              two_factor_send

              +

              When two factor authentication is active, py4web can generate a 6 digit code (using random.randint) and makes it possible to send it to the user. How this code is +sent, is up to you. The two_factor_send argument to the Auth class allows you to specify the method that sends the two factor code to the user.

              This example shows how to send an email with the two factor code:

              def send_two_factor_email(user, code):
                   try:
              @@ -293,15 +303,56 @@ 

              two_factor_sendreturn code

              -

              Notice that this method takes to arguments: the current user, and the code to be sent. +

              Notice that this method takes two arguments: the current user, and the code to be sent. Also notice this method can override the code and return a new one.

              auth.param.two_factor_required = user_outside_network
               auth.param.two_factor_send = send_two_factor_email
               
              +
              +

              two_factor_validate

              +

              By default, py4web will validate the user input in the two factor form by comparing the code entered by the user with the code generated and sent using +two_factor_send. However, sometimes it may be useful to define a custom validation of this user-entered code. For instance, if one would like to use the +TOTP (or the Time-Based One-Time-Passwords) as the two factor authentication method, the validation requires comparing the code entered by the user with the +value generated at the same time at the server side. Hence, it is not sufficient to generate that value earlier when showing the form (using for instance +two_factor_send method), because by the time the user submits the form, the current valid value may already be different. Instead, this value should be +generated when validating the form submitted by the user.

              +

              To accomplish such custom validation, the two_factor_validate method is available. It takes two arguments - the current user and the code that was entered +by the user into the two factor authentication form. The primary use-case for this method is validation of time-based passwords.

              +

              This example shows how to validate a time-based two factor code:

              +
              def validate_code(user, code):
              +   try:
              +      # get the correct code from an external function
              +      correct_code = generate_time_based_code(user_id)
              +   except Exception as e:
              +      # return None to indicate that validation could not be performed
              +      return None
              +
              +   # compare the value entered in the auth form with the correct code
              +   if code == correct_code:
              +      return True
              +   else:
              +      return False
              +
              +
              +

              The validate_code method must return one of three values:

              +
                +
              • True - if the validation succeded,

              • +
              • False - if the validation failed,

              • +
              • None - if the validation was not possible for any reason

              • +
              +

              Notice that - if defined - this method is _always_ called to validate the two factor authentication form. It is up to you to decide what kind of validation it +does. If the returned value is True, the user input will be accepted as valid. If the returned value is False then the user input will be rejected as +invalid, number of tries will be decresed by one, and user will be asked to try again. If the returned value is None the user input will be checked against +the code generated with the use of two_factor_send method and the final result will depend on that comparison. In this case authentication will fail if two_factor_send +method was not defined, and hence no code was sent to the user.

              +
              auth.param.two_factor_validate = validate_code
              +
              +
              +
              -

              two_factor_tries

              +

              two_factor_tries

              By default, the user has 3 attempts to pass two factor authentication. You can override this after using:

              -

              Plugins de Autenticação

              +

              Plugins de Autenticação

              Plugins are defined in “py4web/utils/auth_plugins” and they have a hierarchical structure. Some are exclusive and some are not. For example, default, LDAP, PAM, and SAML are exclusive (the developer has to pick @@ -335,7 +394,7 @@

              Plugins de Autenticação -

              PAM

              +

              PAM

              Configurando PAM é o mais fácil:

              from py4web.utils.auth_plugins.pam_plugin import PamPlugin
               auth.register_plugin(PamPlugin())
              @@ -352,7 +411,7 @@ 

              PAM

              -

              LDAP

              +

              LDAP

              This is a common authentication method, especially using Microsoft Active Directory in enterprises.

              from py4web.utils.auth_plugins.ldap_plugin import LDAPPlugin
               LDAP_SETTING = {
              @@ -370,7 +429,7 @@ 

              LDAP

              -

              OAuth2 with Google

              +

              OAuth2 with Google

              from py4web.utils.auth_plugins.oauth2google import OAuth2Google # TESTED
               auth.register_plugin(OAuth2Google(
                   client_id=CLIENT_ID,
              @@ -381,7 +440,7 @@ 

              OAuth2 with Google -

              OAuth2 with Facebook

              +

              OAuth2 with Facebook

              from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED
               auth.register_plugin(OAuth2Facebook(
                   client_id=CLIENT_ID,
              @@ -392,7 +451,7 @@ 

              OAuth2 with FacebookO ID de cliente e segredo do cliente deve ser fornecido pelo Facebook.

              -

              OAuth2 with Discord

              +

              OAuth2 with Discord

              from py4web.utils.auth_plugins.oauth2discord import OAuth2Discord
               auth.register_plugin(OAuth2Discord(
                   client_id=DISCORD_CLIENT_ID,
              @@ -412,12 +471,13 @@ 

              OAuth2 with Discord -

              Authorization using Tags

              +

              Authorization using Tags

              As already mentioned, authorization is the process of verifying what specific applications, files, and data a user has access to. This is accomplished -in py4web using Tags.

              +in py4web using Tags, that we’ve already discovered on Marcação de registros +in the DAL chapter.

              -

              Etiquetas e permissões

              +

              Etiquetas e permissões

              Py4web provides a general purpose tagging mechanism that allows the developer to tag any record of any table, check for the existence of tags, as well as checking for records @@ -434,12 +494,13 @@

              Etiquetas e permissõesTo use the tagging system you first need to import the Tags module from pydal.tools. Then create a Tags object to tag a table:

              from pydal.tools.tags import Tags
              -groups = Tags(db.auth_user)
              +groups = Tags(db.auth_user, 'groups')
               
              -

              If you look at the database level, a new table will be created with a -name equals to tagged_db + “_tag” + tagged_name, in this case -auth_user_tag_groups:

              +

              The tail_name parameter is optional and if not specified the “default” +value will be used. If you look at the database level, a new table will +be created with a name equals to tagged_db + '_tag_' + tail_name, +in this case auth_user_tag_groups:

              _images/tags_db.png

              Então você pode adicionar uma ou mais marcas de registros da tabela, bem como remover existente tags:

              +

              We’ve already seen a simple requires_membership fixture on :ref:The Condition fixture. It +enables the following syntax:

              +
              groups = Tags(db.auth_user)
              +
              +class requires_membership(Fixture):
              +    def __init__(self, group):
              +        self.__prerequisites__ = [auth.user] # you must have a user before you can check
              +        self.group  = group # store the group when action defined
              +    def on_request(self, context): # will be called if the action is called
              +        if self.group not in groups.get(auth.user_id):
              +            raise HTTP(401) # check and do something
              +
              +@action('index')
              +@action.uses(requires_membership('teacher'))
              +def index():
              +    return 'hello teacher'
              +
              +

              Deixamos para você como um exercício para criar um dispositivo elétrico `` has_membership`` para permitir a seguinte sintaxe:

              -

              Multiple Tags objects

              +

              Multiple Tags objects

              +
              +

              User Impersonation

              +

              Auth provides API that allow you to impersonate another user. +Here is an example of an action to start impersonating and stop impersonating another user.

              +
              @action("impersonate/{user_id:int}", method="GET")
              +@action.uses(auth.user)
              +def start_impersonating(user_id):
              +    if (not auth.is_impersonating() and
              +        user_id and
              +        user_id != auth.user_id and
              +        db(db.auth_user.id==user_id).count()):
              +        auth.start_impersonating(user_id, URL("index"))
              +    raise HTTP(404)
              +
              + @action("stop_impersonating", method="GET")
              + @action.uses(auth)
              + def stop_impersonating():
              +    if auth and auth.is_impersonating():
              +        auth.stop_impersonating(URL("index"))
              +    redirect(URL("index"))
              +
              +
              +
              @@ -547,7 +649,7 @@

              Multiple Tags objects
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Compilado com
              Sphinx usando um @@ -572,7 +674,7 @@

              Multiple Tags objects - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/pt/chapter-14.html b/apps/_documentation/static/pt/chapter-14.html index 934dc443c..343f116ac 100644 --- a/apps/_documentation/static/pt/chapter-14.html +++ b/apps/_documentation/static/pt/chapter-14.html @@ -1,26 +1,28 @@ - + - + - Rede — Documentação py4web 1.20230507.1 - - - - + Rede — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
              - 1.20230507.1 + 20240915
              @@ -57,7 +59,7 @@
            • Ajuda, recursos e dicas
            • Instalação e colocação em funcionamento
            • O Dashboard
            • -
            • Criando seu primeiro aplicativo
            • +
            • Creating an app
            • Fixures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -83,6 +85,7 @@
            • Os campos de referência
            • +
            • Grids with checkboxes
            • De web2py para py4web
            • @@ -114,12 +117,12 @@
              -

              Rede

              +

              Rede

              py4web comes with a Grid object providing grid and CRUD (create, update and delete) capabilities. This allows you to quickly and safely provide an interface to your data. Since it’s also highly customizable, it’s the corner stone of most py4web’s applications.

              -

              Key features

              +

              Key features

              • CRUD completa com Confirmação de exclusão

              • Clique cabeças de coluna para classificar - clique novamente para DESC

              • @@ -138,14 +141,14 @@

                Key features -

                Basic grid example

                +

                Basic grid example

                In this simple example we will make a grid over the superhero table.

                Create a new minimal app called grid. Change it with the following content.

                # in grid/__init__.py
                 import os
                 from py4web import action, Field, DAL
                -from py4web.utils.grid import Grid, GridClassStyleBulma
                -from py4web.utils.form import Form, FormStyleBulma
                +from py4web.utils.grid import *
                +from py4web.utils.form import *
                 from yatl.helpers import A
                 
                 
                @@ -250,7 +253,7 @@ 

                Basic grid examplehttps://github.com/jpsteil/grid_tutorial.

              -

              The Grid object

              +

              The Grid object

              class Grid:
                  def __init__(
                     self,
              @@ -329,7 +332,7 @@ 

              The Grid objectUsing callable parameters later on.

              -

              Searching and filtering

              +

              Searching and filtering

              There are two ways to build a search form:

              -

              CRUD settings

              +

              CRUD settings

              The grid provides CRUD (create, read, update and delete) capabilities utilizing py4web Form. You can turn off CRUD features by setting @@ -354,7 +357,7 @@

              CRUD settings -

              Custom columns

              +

              Custom columns

              If the grid does not involve a join but displays results from a single table you can specify a list of columns. Columns are highly customizable.

              from py4web.utils.grid import Column
              @@ -389,7 +392,7 @@ 

              Custom columns -

              Usando templates

              +

              Usando templates

              Use o seguinte para tornar a sua grid ou formas CRUD em seus templates.

              Mostrar a grid ou um formulário CRUD

              [[=grid.render()]]
              @@ -420,7 +423,7 @@ 

              Usando templates -

              Customizing style

              +

              Customizing style

              Você pode fornecer suas próprias formstyle ou grid classes e estilo ao grid.

              +
              +

              Grids with checkboxes

              +

              While the grid, per se, does not support checkboxes, you can use custom columns to add one or more columns of checkboxes. +You can also add the helpers logic (the grid uses helpers to generate HTML) to wrap it in a <form> and add one +or more submit buttons. You can then add logic to process the selected rows when the button is selected. For example:

              +
              column = Column("select", lambda row: INPUT(_type="checkbox",_name="selected_id",_value=row.id))
              +
              +@action("manage")
              +@action("manage/<path:path>")
              +@action.uses("manage.html", db)
              +def manage(path=None):
              +
              +   grid = Grid(path, db.thing, columns=[column, db.thing.name])
              +
              +   # if we are displaying a "select" grid page (not a form)
              +   if not grid.form:
              +      grid = grid.render()
              +      # if checkboxes selection was submitted
              +      if request.method == "POST":
              +         # do something with the selected ids
              +         print("you selected", request.POST.get("selected_id"))
              +      # inject a ``<form>`` and a ``submit`` button
              +      grid.children[1:] = [FORM(
              +            *grid.children[1:],
              +            DIV(INPUT(_type="submit",_value="do it!")),
              +            _method="POST",
              +            _action=request.url)]
              +   return locals()
              +
              +
              +

              Notice in the above example request.POST.get("selected_id") can be a single ID (if one selected) or a list of IDs (if more than one +is selected).

              +

              @@ -600,7 +636,7 @@

              Os campos de referência
              -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Compilado com
              Sphinx usando um @@ -625,7 +661,7 @@

              Os campos de referência - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/pt/chapter-15.html b/apps/_documentation/static/pt/chapter-15.html index 4cbfedf84..89917b2b5 100644 --- a/apps/_documentation/static/pt/chapter-15.html +++ b/apps/_documentation/static/pt/chapter-15.html @@ -1,26 +1,28 @@ - + - + - De web2py para py4web — Documentação py4web 1.20230507.1 - - - - + De web2py para py4web — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -41,7 +43,7 @@
              - 1.20230507.1 + 20240915
              @@ -57,7 +59,7 @@
            • Ajuda, recursos e dicas
            • Instalação e colocação em funcionamento
            • O Dashboard
            • -
            • Criando seu primeiro aplicativo
            • +
            • Creating an app
            • Fixures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -112,7 +114,7 @@
              -

              De web2py para py4web

              +

              De web2py para py4web

              Este capítulo é dedicado a ajudar os usuários para portar aplicativos web2py antigos para py4web.

              Web2py and py4web share many similarities and some differences. For example they share the same database abstraction layer (pyDAL) which means pydal table definitions and queries are identical @@ -199,9 +201,9 @@

              De web2py para py4web
              -

              Simple conversion examples

              +

              Simple conversion examples

              -

              “Hello world” example

              +

              “Hello world” example

              web2py

              # in controllers/default.py
               def index():
              @@ -217,7 +219,7 @@ 

              “Hello world” example

              -

              “Redirect with variables” example

              +

              “Redirect with variables” example

              web2py

              request.get_vars.name
               request.post_vars.name
              @@ -238,7 +240,7 @@ 

              “Redirect with variables” example -

              “Returning variables” example

              +

              “Returning variables” example

              web2py

              def index():
                  a = request.get_vars.a
              @@ -254,7 +256,7 @@ 

              “Returning variables” example -

              “Returning args” example

              +

              “Returning args” example

              web2py

              def index():
                  a, b, c = request.args
              @@ -270,7 +272,7 @@ 

              “Returning args” example -

              “Return calling methods” example

              +

              “Return calling methods” example

              web2py

              def index():
                  if request.method == "GET":
              @@ -292,7 +294,7 @@ 

              “Return calling methods” example -

              “Setting up a counter” example

              +

              “Setting up a counter” example

              web2py

              def counter():
                  session.counter = (session.counter or 0) + 1
              @@ -307,7 +309,7 @@ 

              “Setting up a counter” example -

              “View” example

              +

              “View” example

              web2py

              {{ extend 'layout.html' }}
               <div>
              @@ -328,7 +330,7 @@ 

              “View” example -

              “Form and flash” example

              +

              “Form and flash” example

              web2py

              db.define_table('thing', Field('name'))
               
              @@ -368,7 +370,7 @@ 

              “Form and flash” example -

              “grid” example

              +

              “grid” example

              web2py

              def index():
                  grid = SQLFORM.grid(db.thing, editable=True)
              @@ -386,7 +388,7 @@ 

              “grid” example -

              “Accessing OS files” example

              +

              “Accessing OS files” example

              web2py

              file_path = os.path.join(request.folder, 'file.csv')
               
              @@ -398,7 +400,7 @@

              “Accessing OS files” example -

              “auth” example

              +

              “auth” example

              web2py

              auth = Auth()
               auth.define_tables()
              @@ -464,7 +466,7 @@ 

              “auth” example -

              © Copyright 2020, BSDv3 License.

              +

              © Copyright 2024, BSDv3 License.

              Compilado com Sphinx usando um @@ -489,7 +491,7 @@

              “auth” example - v: 1.20230507.1 + v: 20240915
              diff --git a/apps/_documentation/static/pt/chapter-16.html b/apps/_documentation/static/pt/chapter-16.html index 41ac0c4cf..05f1068f0 100644 --- a/apps/_documentation/static/pt/chapter-16.html +++ b/apps/_documentation/static/pt/chapter-16.html @@ -1,26 +1,28 @@ - + - + - Advanced topics and examples — Documentação py4web 1.20230507.1 - - - - + Advanced topics and examples — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -40,7 +42,7 @@
              - 1.20230507.1 + 20240915
              @@ -56,7 +58,7 @@
            • Ajuda, recursos e dicas
            • Instalação e colocação em funcionamento
            • O Dashboard
            • -
            • Criando seu primeiro aplicativo
            • +
            • Creating an app
            • Fixures
            • The Database Abstraction Layer (DAL)
            • The RestAPI
            • @@ -68,6 +70,9 @@
            • Rede
            • De web2py para py4web
            • Advanced topics and examples
                +
              • The scheduler
              • +
              • Sending messages using a background task
              • +
              • Celery
              • py4web and asyncio
              • htmx
                • htmx usage in Form
                • @@ -110,9 +115,134 @@
                  -

                  Advanced topics and examples

                  +

                  Advanced topics and examples

                  +
                  +

                  The scheduler

                  +

                  Py4web has a built-in scheduler. There is nothing for you to install or configure to make it work.

                  +

                  Given a task (just a python function), you can schedule async runs of that function. +The runs can be a one-off or periodic. They can have timeout. They can be scheduled to run at a given scheduled time.

                  +

                  The scheduler works by creating a table task_run and enqueueing runs of the predefined task as table records. +Each task_run references a task and contains the input to be passed to that task. The scheduler will capture the +task stdout+stderr in a db.task_run.log and the task output in db.task_run.output.

                  +

                  A py4web thread loops and finds the next task that needs to be executed. For each task it creates a worker process +and assigns the task to the worker process. You can specify how many worker processes should run concurrently. +The worker processes are daemons and they only live for the life of one task run. Each worker process is only +responsible for executing that one task in isolation. The main loop is responsible for assigning tasks and timeouts.

                  +

                  The system is very robust because the only source of truth is the database and its integrity is guaranteed by +transactional safety. Even if py4web is killed, running tasks continue to run unless they complete, fail, or are +explicitly killed.

                  +

                  Aside for allowing multiple concurrent task runs in execution on one node, +it is also possible to run multiple instances of the scheduler on different computing nodes, +as long as they use the same client/server database for task_run and as long as +they all define the same tasks.

                  +

                  Here is an example of how to use the scheduler:

                  +
                  from pydal.tools.scheduler import Scheduler, delta, now
                  +from .common import db
                  +
                  +# create and start the scheduler
                  +scheduler = Scheduler(db, sleep_time=1, max_concurrent_runs=1)
                  +scheduler.start()
                  +
                  +# register your tasks
                  +scheduler.register_task("hello", lambda **inputs: print("hi!"))
                  +scheduler.register_task("slow", lambda: time.sleep(10))
                  +scheduler.register_task("periodic", lambda **inputs: print("I am periodic!"))
                  +scheduler.register_task("fail", lambda x: 1 / x)
                  +
                  +# enqueue some task runs:
                  +
                  +scheduler.enqueue_run(name="hello")
                  +scheduler.enqueue_run(name="hello", scheduled_for=now() + delta(10) # start in 10 secs
                  +scheduler.enqueue_run(name="slow", timeout=1) # 1 secs
                  +scheduler.enqueue_run(name="periodic", period=10) # 10 secs
                  +scheduler.enqueue_run(name="fail", inputs={"x": 0})
                  +
                  +
                  +

                  Notice that in scaffolding app, the scheduler is created and started in common if +USE_SCHEDULER=True in settings.py.

                  +

                  You can manage your task runs busing the dashboard or using a Grid(path, db.task_run).

                  +

                  To prevent database locks (in particular with sqlite) we recommend:

                  +
                    +
                  • Use a different database for the scheduler and everything else

                  • +
                  • Always db.commit() as soon as possible after any insert/update/delete

                  • +
                  • wrap your database logic in tasks in a try…except as in

                  • +
                  +
                  def my_task():
                  +    try:
                  +        # do something
                  +        db.commit()
                  +    except Exception:
                  +        db.rollback()
                  +
                  +
                  +
                  +
                  +

                  Sending messages using a background task

                  +

                  As en example of application of the above, consider the case of wanting to send emails asynchronously from a background task. +In this example we send them using SendGrid from Twilio (https://www.twilio.com/docs/sendgrid/for-developers/sending-email/quickstart-python).

                  +

                  Here is a possible scheduler task to send the email:

                  +
                  import sendgrid
                  +from sendgrid.helpers.mail import Mail, Email, To, Content
                  +
                  +def sendmail_task(from_addr, to_addrs, subject, body):
                  +    ""
                  +    # build the messages using sendgrid API
                  +    from_email = Email(from_addr)  # Must be your verified sender
                  +    content_type = "text/plain" if body[:6] != "<html>" else "text/html"
                  +    content = Content(content_type, body)
                  +    mail = Mail(from_email, To(to_addrs), subject, content)
                  +    # ask sendgrid to deliver it
                  +    sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY)
                  +    response = sg.client.mail.send.post(request_body=mail.get())
                  +    # check if worked
                  +    assert response.status_code == "200"
                  +
                  +# register the above task with the scheduler
                  +scheduler.register_task("sendmail", sendmail_task)
                  +
                  +
                  +

                  To schedule sending a new email do:

                  +
                  email = {
                  +    "from_addr": "me@example.com",
                  +    "to_addrs": ["me@example.com"],
                  +    "subject": "Hello World",
                  +    "body": "I am alive!",
                  +}
                  +scheduler.enqueue_run(name="sendmail", inputs=email, scheduled_for=None)
                  +
                  +
                  +

                  The key:value in the email representation must match the arguments of the task. +The scheduled_for argument is optional and allows you to specify when the email should be sent. +You can use the Dashboard to see the status of your task_runs for the task called sendmail.

                  +

                  You can also tell auth to tap into above mechanism for sending emails:

                  +
                  class MySendGridSender:
                  +    def __init__(self, from_addr):
                  +        self.from_addr = from_adds
                  +    def send(self, to_addr, subject, body):
                  +        email = {
                  +            "from_addr": self.from_addr,
                  +            "to_addrs": [to_addr],
                  +            "subject": subject,
                  +            "body": body,
                  +        }
                  +        scheduler.enqueue_run(name="sendmail", inputs=email)
                  +
                  +auth.sender = MySendGridSender(from_addr="me@example.com")
                  +
                  +
                  +

                  With the above, Auth will not send emails using smtplib. Instead it will send them with SendGrid using the scheduler. +Notice the only requirement here is that auth.sender must be an object with a send method with the same signature as in the example.

                  +

                  Notice, it it also possible to send SMS messages instead of emails but this requires 1) store the phone number in auth_user and 2) override the Auth.send method.

                  +
                  +
                  +

                  Celery

                  +

                  Yes. You can use Celery instead of the build-in scheduler but it adds complexity and it is less robust. +Yet the build-in scheduler is designed for long running tasks and the database can become a bottleneck +if you have hundreds of tasks running concurrently. Celery may work better if you have more than 100 concurrent +tasks and/or they are short running tasks.

                  +
                  -

                  py4web and asyncio

                  +

                  py4web and asyncio

                  Asyncio is not strictly needed, at least for most of the normal use cases where it will add problems more than value because of its concurrency model. On the other hand, we think py4web needs a built-in websocket async based solution.

                  @@ -121,7 +251,7 @@

                  py4web and asyncio -

                  htmx

                  +

                  htmx

                  There are many javascript front-end frameworks available today that allow you great flexibility over how you design your web client. Vue, React and Angular are just a few. However, the complexity in building one of these systems prevents many developers from reaping those benefits. @@ -140,7 +270,7 @@

                  htmx
                • Includes an htmx attributes plugin for the py4web grid

                • -

                  htmx usage in Form

                  +

                  htmx usage in Form

                  The py4web Form class allows you to pass **kwargs to it that will be passed along as attributes to the html form. For example, to add the hx-post and hx-target to the <form> element you would use:

                  attrs = {
                  @@ -232,7 +362,7 @@ 

                  htmx usage in Form -

                  htmx usage in Grid

                  +

                  htmx usage in Grid

                  The py4web grid provides an attributes plugin system that allows you to build plugins to provide custom attributes for form elements, anchor elements or confirmation messages. py4web also provide an attributes plugin specifically for htmx.

                  @@ -303,7 +433,7 @@

                  htmx usage in Grid -

                  Autocomplete Widget using htmx

                  +

                  Autocomplete Widget using htmx

                  htmx can be used for much more than just form/grid processing. In this example we’ll take advantage of htmx and the py4web form widgets to build an autocomplete widget that can be used in your forms. NOTE: this is just an example, none of this code comes with py4web

                  @@ -507,18 +637,18 @@

                  Autocomplete Widget using htmx -

                  utils.js

                  +

                  utils.js

                  Multiple times in this documentation we have mentioned utils.js which comes with the scaffolding application, yet we never clearly listed what is in there. So here it is.

                  -

                  string.format

                  +

                  string.format

                  It extends the String object prototype to allow expressions like this:

                  var a = "hello {name}".format(name="Max");
                   
                  -

                  The Q object

                  +

                  The Q object

                  The Q object can be used like a selector supporting jQuery like syntax:

                  var element = Q("#element-id")[0];
                   var selected_elements = Q(".element-class");
                  @@ -602,7 +732,7 @@ 

                  The Q object -

                  The T object

                  +

                  The T object

                  This is a Javascript reimplementation of the Python pluralize library in Python which is used by the Python T object in py4web. So basically a client-side T.

                  T.translations = {'dog': {0: 'no cane', 1: 'un case', 2: '{n} cani', 10: 'tanti cani'}};
                  @@ -732,7 +862,7 @@ 

                  The T object -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Compilado com Sphinx usando um @@ -757,7 +887,7 @@

                  The T object - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/pt/genindex.html b/apps/_documentation/static/pt/genindex.html index 127eb801e..e7bb06847 100644 --- a/apps/_documentation/static/pt/genindex.html +++ b/apps/_documentation/static/pt/genindex.html @@ -1,26 +1,28 @@ - + - Índice — Documentação py4web 1.20230507.1 - - - - + Índice — Documentação py4web 20240915 + + + + + + - - - - - - - - + + + + + + + + @@ -39,7 +41,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -55,7 +57,7 @@
                • Ajuda, recursos e dicas
                • Instalação e colocação em funcionamento
                • O Dashboard
                • -
                • Criando seu primeiro aplicativo
                • +
                • Creating an app
                • Fixures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -108,7 +110,7 @@

                  Índice


                  -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Compilado com Sphinx usando um @@ -133,7 +135,7 @@

                  Índice

                  - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/pt/index.html b/apps/_documentation/static/pt/index.html index 2e5d37860..5b154ccc9 100644 --- a/apps/_documentation/static/pt/index.html +++ b/apps/_documentation/static/pt/index.html @@ -1,26 +1,28 @@ - + - + - py4web: o manual de referência — Documentação py4web 1.20230507.1 - - - - + py4web: o manual de referência — Documentação py4web 20240915 + + + + + + - - - - - - - + + + + + + + @@ -40,7 +42,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -56,7 +58,7 @@
                • Ajuda, recursos e dicas
                • Instalação e colocação em funcionamento
                • O Dashboard
                • -
                • Criando seu primeiro aplicativo
                • +
                • Creating an app
                • Fixures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -95,7 +97,7 @@
                  -

                  py4web: o manual de referência

                  +

                  py4web: o manual de referência

                  Compilado com Sphinx usando um @@ -276,7 +284,7 @@

                  Índices e tabelas - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/pt/objects.inv b/apps/_documentation/static/pt/objects.inv index 45d5e01e8..80244317c 100644 Binary files a/apps/_documentation/static/pt/objects.inv and b/apps/_documentation/static/pt/objects.inv differ diff --git a/apps/_documentation/static/pt/search.html b/apps/_documentation/static/pt/search.html index 5a8463000..56fd2f7bf 100644 --- a/apps/_documentation/static/pt/search.html +++ b/apps/_documentation/static/pt/search.html @@ -1,27 +1,29 @@ - + - Pesquisar — Documentação py4web 1.20230507.1 - - - - + Pesquisar — Documentação py4web 20240915 + + + + + + - - - - - - - - + + + + + + + + @@ -42,7 +44,7 @@
                  - 1.20230507.1 + 20240915
                  @@ -58,7 +60,7 @@
                • Ajuda, recursos e dicas
                • Instalação e colocação em funcionamento
                • O Dashboard
                • -
                • Criando seu primeiro aplicativo
                • +
                • Creating an app
                • Fixures
                • The Database Abstraction Layer (DAL)
                • The RestAPI
                • @@ -115,7 +117,7 @@
                  -

                  © Copyright 2020, BSDv3 License.

                  +

                  © Copyright 2024, BSDv3 License.

                  Compilado com Sphinx usando um @@ -140,7 +142,7 @@ - v: 1.20230507.1 + v: 20240915
                  diff --git a/apps/_documentation/static/pt/searchindex.js b/apps/_documentation/static/pt/searchindex.js index 483023026..53f59cf17 100644 --- a/apps/_documentation/static/pt/searchindex.js +++ b/apps/_documentation/static/pt/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["chapter-01", "chapter-02", "chapter-03", "chapter-04", "chapter-05", "chapter-06", "chapter-07", "chapter-08", "chapter-09", "chapter-10", "chapter-11", "chapter-12", "chapter-13", "chapter-14", "chapter-15", "chapter-16", "index"], "filenames": ["chapter-01.rst", "chapter-02.rst", "chapter-03.rst", "chapter-04.rst", "chapter-05.rst", "chapter-06.rst", "chapter-07.rst", "chapter-08.rst", "chapter-09.rst", "chapter-10.rst", "chapter-11.rst", "chapter-12.rst", "chapter-13.rst", "chapter-14.rst", "chapter-15.rst", "chapter-16.rst", "index.rst"], "titles": ["O que \u00e9 py4web?", "Ajuda, recursos e dicas", "Instala\u00e7\u00e3o e coloca\u00e7\u00e3o em funcionamento", "O Dashboard", "Criando seu primeiro aplicativo", "Fixures", "The Database Abstraction Layer (DAL)", "The RestAPI", "Linguagem de template YATL", "Helpers YATL", "Internacionaliza\u00e7\u00e3o", "Foruml\u00e1rios", "Authentication and authorization", "Rede", "De web2py para py4web", "Advanced topics and examples", "py4web: o manual de refer\u00eancia"], "terms": {"is": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "web": [0, 1, 2, 5, 6, 8, 12, 13, 14, 15, 16], "framework": [0, 2, 11, 12, 13, 14, 15], "rapid": [0, 11, 15], "development": [0, 1, 4, 6], "of": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "efficient": 0, "databas": [0, 1, 3, 4, 7, 12, 13, 14, 15, 16], "driven": 0, "applications": [0, 2, 3, 4, 6, 11, 12, 13], "it": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "an": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "evolution": 0, "the": [0, 3, 8, 9, 10, 12, 14, 16], "popul": [0, 6], "web2py": [0, 1, 2, 3, 4, 6, 11, 12, 13, 16], "but": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "much": [0, 1, 5, 6, 8, 11, 13, 14, 15], "fast": [0, 4, 6, 8, 14, 15], "and": [0, 1, 2, 3, 4, 5, 9, 16], "slick": 0, "its": [0, 1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "internal": [0, 1, 8, 12], "design": [0, 12, 15, 16], "has": [0, 2, 4, 5, 6, 8, 11, 12, 13, 14, 15], "been": [0, 2, 5, 6, 8, 11], "simplified": [0, 4, 8], "compared": 0, "to": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "be": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "seen": [0, 5, 7, 9, 11, 13, 15], "competitor": 0, "other": [0, 1, 2, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15], "frameworks": [0, 2, 5, 14, 15], "lik": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15], "djang": [0, 1, 14], "or": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "flask": [0, 14], "indeed": [0, 5], "serv": [0, 2, 3, 4, 7, 8, 12, 14, 15, 16], "sam": [0, 2, 5, 6, 7, 8, 11, 12, 14, 15], "purpos": [0, 4, 6, 9, 11, 12, 14], "yet": [0, 2, 4, 5, 8, 11, 15], "aims": 0, "provid": [0, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "larg": [0, 11], "featur": [0, 4, 6, 7, 8, 14, 16], "set": [0, 2, 5, 7, 8, 9, 10, 13, 14, 15], "out": [0, 1, 5, 8, 11, 15], "box": [0, 6, 11], "reduc": [0, 15], "tim": [0, 4, 5, 6, 8, 13, 14, 15], "new": [0, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "apps": [0, 1, 2, 3, 4, 5, 6, 8, 12, 14], "from": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "historical": 0, "perspectiv": 0, "our": [0, 1, 2, 5, 6, 9, 11, 15], "story": 0, "starts": [0, 3, 5, 8, 14], "in": [0, 1, 2, 3, 4, 6, 7, 10, 11, 12, 13, 14, 16], "2007": 0, "when": [0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "was": [0, 5, 6, 11, 12], "first": [0, 2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15], "released": 0, "designed": [0, 4, 6, 8, 11], "all": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "inclusiv": [0, 11], "solution": [0, 15], "one": [0, 2, 4, 5, 6, 7, 8, 11, 12, 13, 15], "zip": [0, 2, 6, 11], "fil": [0, 1, 2, 3, 5, 6, 8, 9, 10, 12, 13, 15, 16], "containing": [0, 2, 5, 11, 12, 13, 15], "python": [0, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15], "interpret": [0, 6], "based": [0, 3, 4, 5, 7, 8, 11, 12, 13, 14, 15], "ide": [0, 1, 2, 3, 6], "collection": [0, 14], "battl": 0, "tested": [0, 2, 9, 12], "packag": [0, 6, 9], "that": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "work": [0, 2, 4, 5, 6, 8, 9, 11, 15], "well": [0, 5, 6, 8, 11, 12, 15], "togeth": [0, 8], "many": [0, 1, 2, 4, 5, 7, 8, 11, 13, 14, 15], "ways": [0, 4, 6, 12, 13, 15], "immensely": 0, "successful": [0, 12], "succeeded": 0, "providing": [0, 13, 14], "low": [0, 11], "barri": 0, "entry": [0, 6, 14], "developers": [0, 1, 6, 12, 15], "very": [0, 4, 8, 9, 11, 12, 14], "secur": [0, 11], "platform": [0, 1], "remains": 0, "backwards": [0, 11], "compatibl": [0, 1, 9, 11], "until": [0, 5, 6, 8, 11], "today": [0, 11, 15], "always": [0, 2, 4, 5, 6, 11, 15], "suffered": 0, "probl": [0, 6, 8, 13], "monolithic": 0, "most": [0, 4, 5, 6, 11, 12, 13, 14, 15], "experienced": 0, "did": [0, 4, 15], "not": [0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15], "understand": [0, 2, 4, 6, 7, 8], "how": [0, 2, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15], "use": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "components": [0, 3, 9, 11, 15], "outsid": [0, 5, 6, 11, 13, 14, 15], "third": [0, 5, 11], "party": [0, 5], "within": [0, 1, 5, 6, 8, 9, 11, 15], "we": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "thought": [0, 6, 12], "perfect": 0, "tool": 0, "hav": [0, 1, 2, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "broken": [0, 6, 11], "into": [0, 2, 4, 5, 6, 8, 9, 11, 14, 15], "piec": [0, 11], "becaus": [0, 4, 5, 6, 8, 9, 11, 13, 15], "would": [0, 5, 6, 9, 11, 15], "compromis": 0, "security": [0, 7, 12], "turned": 0, "wer": [0, 4], "wrong": [0, 5, 6], "playing": 0, "with": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 15], "others": [0, 6, 14], "important": [0, 2, 4, 5, 6, 11, 12], "henc": [0, 2, 5, 6, 11], "sinc": [0, 2, 4, 5, 7, 8, 11, 13, 14], "2015": 0, "worked": 0, "on": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15], "thre": [0, 5, 6], "fronts": 0, "N\u00f3s": [0, 1, 5], "port": [0, 2, 3, 5, 6, 14], "par": [0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 13, 15, 16], "3": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "quebr": 0, "m\u00f3dul": [0, 1, 2, 5, 6], "pod": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 13], "ser": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13], "usad": [0, 4, 6, 9, 10, 12, 13], "form": [0, 2, 4, 5, 6, 7, 10, 12, 13, 16], "independent": [0, 5, 6, 12], "reagrup": 0, "alguns": [0, 2, 4, 5, 6, 7, 8, 9], "dess": [0, 5, 6, 12], "nov": [0, 1, 2, 3, 4, 13], "modul": [0, 2, 4, 6, 8, 9, 11, 12, 13, 14], "quadr": [0, 1, 6], "mor": [0, 2, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15], "than": [0, 5, 6, 8, 9, 11, 12, 13, 15], "repackaging": 0, "complet": [0, 2, 6, 9, 11, 13], "redesign": 0, "uses": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "som": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 14], "them": [0, 1, 2, 4, 5, 6, 7, 8, 11, 12, 13, 14], "cas": [0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 14, 15], "bett": [0, 1, 2, 4, 6, 13, 14], "functionality": [0, 8, 14], "removed": [0, 6, 9, 11], "added": [0, 2, 5, 11, 12, 15], "tried": 0, "preserv": [0, 5], "syntax": [0, 1, 4, 5, 6, 7, 8, 9, 11, 14, 15], "users": [0, 1, 4, 5, 12, 14], "loved": 0, "her": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15], "explicit": [0, 2, 4, 5, 6, 8, 11], "list": [0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15], "see": [0, 3, 4, 5, 6, 8, 9, 11, 12, 13, 15], "De": [0, 1, 4, 6, 16], "details": [0, 1, 5, 6, 7, 8, 11, 13], "if": [0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15], "you": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "come": [0, 5], "contr\u00e1ri": [0, 4, 6, 8], "requ": [0, 2, 5, 6, 11, 12, 13, 14, 15], "unlik": [0, 2, 7, 8, 14, 15], "installed": [0, 1, 2, 3, 4, 5, 6], "using": [0, 1, 3, 4, 6, 7, 11, 14, 16], "pip": [0, 1, 6], "dependenc": [0, 2, 5, 14], "are": [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "managed": 0, "requirements": [0, 2, 11], "txt": [0, 2, 4, 6], "regul": [0, 2, 4, 6, 7, 8, 10, 11, 13, 14, 15], "this": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "different": [0, 2, 5, 6, 8, 11, 12, 14, 15], "particul": [0, 1, 6, 11, 13, 14, 15], "ditched": 0, "custom": [0, 4, 6, 8, 9, 14, 15, 16], "import": [0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "rely": [0, 4, 6], "now": [0, 4, 5, 6, 11, 13, 15], "exclusively": [0, 5, 6], "mechanism": [0, 6, 8, 9, 12, 14], "m\u00faltipl": [0, 6], "aplic": [0, 2, 3, 5, 13, 14, 16], "concorrent": 0, "enquant": [0, 6, 8], "s\u00e3": [0, 1, 3, 4, 5, 6, 8, 9, 10, 12, 13], "subm\u00f3dul": 0, "ombott": [0, 4, 14], "reduced": 0, "spin": [0, 4], "off": [0, 2, 4, 13, 15], "bottl": [0, 4, 5, 8, 14], "request": [0, 1, 2, 5, 6, 7, 11, 12, 13, 14, 15], "object": [0, 4, 5, 6, 7, 8, 9, 11, 12, 14, 16], "routing": [0, 4, 14], "does": [0, 2, 5, 6, 8, 11, 12, 13, 14, 15], "creat": [0, 1, 2, 4, 5, 6, 8, 11, 12, 13, 15], "environment": [0, 1, 4, 5, 6], "at": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "every": [0, 5, 6, 12, 13, 14, 15], "introduc": 0, "concept": [0, 12], "fixtur": [0, 2, 6, 9, 14, 16], "explicitly": [0, 2, 5, 6, 8, 9, 11], "declar": [0, 4, 5, 6, 8], "which": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "objects": [0, 4, 5, 6, 9, 11, 13, 14, 15], "need": [0, 1, 2, 5, 6, 8, 9, 11, 12, 13, 14, 15], "re": [0, 1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "initialized": 0, "http": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "arriv": [0, 6], "needs": [0, 4, 5, 6, 12, 14, 15], "cleanup": 0, "completed": [0, 6], "mak": [0, 1, 2, 5, 6, 8, 9, 11, 12, 13, 14, 15], "session": [0, 2, 4, 6, 9, 11, 12, 13, 14, 15, 16], "s": [0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15], "strong": [0, 9, 12], "encryption": 0, "dat": [0, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "sessions": [0, 6, 14], "long": [0, 6, 11], "stored": [0, 5, 6, 11, 12, 14, 15], "system": [0, 2, 5, 6, 9, 12, 15], "created": [0, 2, 4, 5, 6, 8, 11, 12], "performanc": [0, 5, 6, 14], "issu": [0, 6], "cooki": [0, 4, 6, 15], "red": [0, 3, 4, 6, 8, 9, 11, 16], "memcach": [0, 6], "optionally": [0, 2], "also": [0, 1, 2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "limited": [0, 5, 14, 15], "json": [0, 1, 2, 4, 5, 6, 7, 10, 11, 14, 15], "serializabl": [0, 5, 6], "built": [0, 1, 4, 6, 8, 11, 15, 16], "sistem": [0, 2, 5, 6], "bilh\u00e9t": 0, "global": [0, 6, 14, 15], "n\u00e3": [0, 1, 2, 3, 4, 5, 6, 8, 9, 12, 13], "Os": [0, 3, 4, 8, 9, 12, 16], "bilhet": [0, 3, 6], "armazen": [0, 2, 5, 6, 9], "arquiv": [0, 2, 3, 4, 5, 6, 12, 16], "individu": [0, 2, 5, 6], "Eles": [0, 4, 6], "\u00fanic": [0, 2, 6, 9], "banc": [0, 1, 3, 4, 5, 12], "dad": [0, 1, 3, 4, 5, 12, 16], "pydal": [0, 2, 3, 4, 5, 6, 7, 11, 12, 13, 14, 15], "leverag": 0, "restap": [0, 3, 16], "usa": [0, 4, 5, 6, 8], "linguag": [0, 4, 5, 16], "templat": [0, 4, 9, 11, 12, 14, 15, 16], "yatl": [0, 4, 5, 6, 11, 13, 15, 16], "padr\u00e3": [0, 2, 4, 5, 8, 9, 13], "suport": [0, 10, 16], "delimit": [0, 6, 8], "evit": [0, 1, 2, 6, 8, 11], "conflit": [0, 2, 4, 6], "model": [0, 3, 7, 9, 14, 15], "js": [0, 4, 5, 8, 10, 14, 16], "vue": [0, 4, 15], "angularjs": 0, "inclu": [0, 4, 6, 8, 9, 13], "subconjunt": [0, 6], "ajud": [0, 2, 4, 6, 14, 16], "bibliotec": [0, 4, 10], "pluraliz": [0, 4, 5, 13, 15, 16], "internacionaliz": [0, 4, 16], "Na": 0, "pr\u00e1tic": [0, 6, 7], "exp\u00f5": [0, 3, 4, 6, 12], "objet": [0, 5, 6, 8, 9, 10], "t": [0, 2, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14], "muit": [0, 1, 2, 4, 5], "semelh": [0, 4, 5, 6, 9], "fornec": [0, 2, 3, 4, 5, 6, 9, 12, 13], "melhor": [0, 1, 6, 10, 13, 16], "cach": [0, 2, 4, 5, 11], "capac": 0, "flex\u00edv": 0, "vem": [0, 4, 6], "painel": [0, 2, 3], "app": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "administr": [0, 2, 6], "substitu": [0, 2, 6, 8, 9], "Esta": [0, 1, 2, 4, 6], "carreg": [0, 6], "gest\u00e3": [0, 1], "edi\u00e7\u00e3": 0, "interfac": [0, 3, 5, 6, 13, 14, 15], "bas": [0, 2, 4, 8, 13, 15], "Isto": [0, 2, 3, 4, 5, 6, 9, 13], "funcional": [0, 2, 5, 6], "appadmin": [0, 6], "comes": [0, 5, 8, 11, 12, 13, 14, 15], "grid": [0, 16], "simil": [0, 11, 14], "sqlform": [0, 11, 14], "auth": [0, 2, 4, 6, 8, 9, 15, 16], "f\u00e1cil": [0, 6, 8, 9, 12], "estend": [0, 5, 6], "Fora": 0, "caix": [0, 6, 9], "b\u00e1sic": [0, 1, 6, 16], "regist": [0, 4, 5, 6, 8, 12, 13, 14], "login": [0, 2, 3, 4, 5, 6, 8, 11, 12, 14], "logout": [0, 8, 12], "alter": [0, 2, 4, 5, 6, 10], "senh": [0, 2, 3, 5, 6, 12], "solicit": [0, 6], "edit": [0, 2, 3, 4, 8, 11, 12, 13, 14, 15], "perfil": [0, 12], "bem": [0, 4, 5, 6, 12], "integr": 0, "pam": [0, 5], "saml2": 0, "ldap": [0, 5], "oauth2": [0, 5], "googl": [0, 3, 5, 8, 11, 13], "facebook": [0, 5], "twitt": [0, 5, 12], "tags": [0, 5, 6, 8, 9, 14, 15, 16], "tag": [0, 6, 7, 8, 12, 14, 15], "groups": [0, 1, 3, 5, 12, 14], "search": [0, 6, 11, 12, 13, 16], "by": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "apply": [0, 5, 11, 12, 13], "permissions": [0, 5, 6, 12, 14], "membership": [0, 5, 11, 12, 14], "component": [0, 4, 5, 8, 9, 11, 12, 15], "personaliz": [0, 2, 16], "projet": [0, 2], "interag": 0, "geral": [0, 2, 6, 7, 9], "Essas": 0, "apis": [0, 6, 12, 14], "permit": [0, 3, 5, 6, 8, 9, 12, 13], "servidor": [0, 4, 5, 6], "defin": [0, 4, 5, 8, 9, 11, 13, 15], "pol\u00edt": 0, "sobr": [0, 8], "qua": [0, 6], "oper": [0, 3, 10, 16], "client": [0, 7, 11, 12, 15], "execut": [0, 2, 3, 6, 11], "d\u00e1": [0, 2, 13], "flexibil": [0, 2, 13], "dentr": [0, 2, 4, 6, 13], "restri\u00e7\u00f5": [0, 6], "dois": [0, 5, 6, 8], "princip": 0, "mtabl": 0, "grad": 0, "api": [0, 6, 7, 9, 11, 12, 15], "continu": [0, 6, 8, 15], "mesm": [0, 1, 2, 4, 5, 8, 9, 10, 13], "desenvolv": 0, "acess": [0, 2, 4, 5, 6], "produ\u00e7\u00e3": 0, "r\u00e1p": [0, 6], "segur": [0, 5], "thanks": 0, "everyon": [0, 1], "who": [0, 12], "contributed": 0, "project": [0, 2, 4, 5, 6], "especially": [0, 2, 5, 11, 12], "massim": [0, 6], "di": [0, 5], "pierr": 0, "luc": [0, 1], "alfar": [0, 1], "cassi": 0, "botar": 0, "dan": 0, "carroll": 0, "jim": [0, 1, 13], "steil": [0, 1, 13], "john": [0, 6], "m": [0, 2, 6, 9, 11], "wolf": 0, "micah": 0, "beasley": 0, "nic": [0, 15], "zanferrar": 0, "pirsch": 0, "sugiz": 0, "valq7711": [0, 4], "kevin": 0, "kell": 0, "log": [0, 2, 4, 5, 6, 8, 9, 12, 14, 15], "special": [0, 5, 6, 8, 12, 14, 16], "official": [0, 11, 15], "friendly": [0, 5, 8], "call": [0, 5, 6, 8, 11, 12, 15], "axel": 0, "axolotl": 0, "magically": 0, "represents": [0, 6], "sens": [0, 11], "kindness": 0, "inclusion": 0, "believ": [0, 5], "cornerston": 0, "growing": [0, 15], "community": [0, 6], "fiz": 1, "noss": [1, 4, 5, 6], "torn": [1, 2, 5, 6, 9, 13], "simpl": [1, 2, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16], "limp": 1, "Mas": [1, 4, 6], "voc": [1, 2, 3, 4, 5, 6, 8, 9, 12, 13], "sab": [1, 2, 6, 8], "program": [1, 3, 6, 8], "\u00e9": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], "taref": [1, 5, 6, 12], "dif\u00edcil": [1, 2], "Ela": [1, 6, 8], "exig": [1, 2, 6, 12], "ment": [1, 6], "abert": [1, 3, 6], "capaz": [1, 2], "salt": [1, 11], "frequ\u00eanc": 1, "perd": [1, 2, 6], "html": [1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15], "javascript": [1, 4, 7, 9, 15], "css": [1, 4, 5, 8, 9, 11, 12, 13, 15], "tenh": [1, 5, 6], "med": 1, "nest": [1, 6, 8, 9], "vam": [1, 2, 6, 9], "lo": [1, 2, 3, 4, 6, 7, 8, 9], "lad": [1, 2, 5, 6], "jorn": 1, "E": [1, 6, 12], "outr": [1, 2, 4, 5, 9, 10, 12, 13, 16], "valios": 1, "mostr": [1, 4, 6, 13], "referenc": [1, 6, 7, 8, 9, 11, 13], "availabl": [1, 2, 6, 8, 9, 12, 14, 15], "onlin": [1, 6], "https": [1, 3, 4, 5, 7, 8, 11, 12, 13, 15], "_documentation": [1, 3], "static": [1, 4, 8, 9, 14], "index": [1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "wher": [1, 2, 4, 6, 8, 11, 12, 14, 15], "ll": [1, 2, 3, 5, 6, 8, 11, 13, 15], "find": [1, 2, 4, 11, 12, 13], "pdf": [1, 11], "ebook": 1, "version": [1, 3, 7, 8, 11, 15], "multipl": [1, 2, 6, 8, 9, 11, 14, 15, 16], "languag": [1, 4, 5, 8, 10, 14, 15], "written": [1, 8, 11, 13], "restructuredtext": 1, "generated": [1, 5, 6, 8, 11, 13, 15], "sphinx": 1, "exist": [1, 2, 4, 5, 6, 8, 11, 15], "discuss\u00e3": 1, "dedic": [1, 4, 14], "hosped": 1, "consult": [1, 7, 12, 13], "g": [1, 6, 9, 11], "principal": [1, 5, 6, 16], "discuss\u00f5": 1, "desenvolvedor": [1, 8, 12, 13], "usu\u00e1ri": [1, 2, 3, 4, 5, 6, 12, 14], "qualqu": [1, 2, 4, 5, 6, 7, 8, 12], "problem": [1, 6], "dev": [1, 2, 3, 4, 5, 6, 8, 9, 12, 13], "enfrent": [1, 6], "lug": [1, 5, 6], "cert": [1, 2, 6, 13], "procur": [1, 6], "solu\u00e7\u00e3": [1, 2, 6], "For": [1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 14, 15], "quick": [1, 11], "questions": [1, 2], "chats": 1, "fre": [1, 5, 12], "dedicated": [1, 5, 9], "could": [1, 2, 4, 6, 8, 9, 11, 12, 14], "usually": [1, 5, 7, 11], "hanging": 1, "channel": 1, "ther": [1, 2, 5, 6, 8, 11, 12, 13, 14, 15], "tutorials": 1, "vid": [1, 2, 15], "learn": [1, 6], "sit": [1, 2, 4, 5, 6, 9, 13, 15], "lots": 1, "excellent": [1, 13], "training": 1, "cours": [1, 8], "2020": 1, "uc": 1, "sant": 1, "cruz": [1, 6], "blog": [1, 6, 9], "andrew": 1, "gavgavian": 1, "replicat": [1, 5], "famous": 1, "corey": 1, "schaf": 1, "tutorial": [1, 2, 4, 13, 15], "seri": 1, "creating": [1, 2, 6, 12, 14], "south": 1, "breez": 1, "enterpris": [1, 12], "dem": [1, 2, 15], "around": [1, 14], "structur": [1, 4, 6, 12, 13, 14, 16], "microsoft": [1, 12], "northwind": 1, "converted": [1, 6, 11], "sqlit": [1, 4, 5, 7, 11, 12, 13], "view": [1, 5, 6], "final": [1, 2, 5, 6, 10], "result": [1, 4, 6, 7, 8, 11, 13, 15], "last": [1, 5, 8, 11, 12, 13, 15], "least": [1, 2, 11, 14, 15], "open": [1, 2, 4, 5, 6, 9], "sourc": [1, 2, 3, 4, 6, 11], "bsd": 1, "v3": 1, "licens": 1, "hosted": 1, "means": [1, 3, 5, 6, 8, 11, 12, 14], "read": [1, 4, 5, 6, 7, 13, 15], "study": 1, "experiment": [1, 11], "yourself": [1, 11], "par\u00e1graf": [1, 9], "prelimin": 1, "\u00fate": [1, 6], "antes": [1, 6, 8, 9, 12, 13], "comec": [1, 2, 4, 6], "aprend": 1, "A": [1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16], "fim": [1, 2, 5, 6], "compreend": [1, 13], "precis": [1, 2, 4, 5, 6, 13], "pel": [1, 4, 5, 6, 8, 9, 10, 12], "men": [1, 2, 5, 6], "conhec": [1, 6], "H\u00e1": [1, 5, 6, 13], "livr": [1, 6], "curs": 1, "dispon\u00edv": 1, "escolh": [1, 4, 6], "decor": [1, 4, 16], "marc": [1, 6, 9, 12], "total": [1, 6, 7], "following": [1, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "chapters": [1, 5], "will": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "start": [1, 2, 3, 4, 5, 6, 9, 11, 13], "coding": 1, "your": [1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "comput": 1, "suggest": [1, 11], "setup": [1, 3, 4, 12, 15], "workplac": 1, "plan": [1, 15], "efficiently": [1, 14], "safely": [1, 13], "even": [1, 4, 5, 6, 8, 9, 11, 13, 14], "running": [1, 2, 5], "exampl": [1, 2, 4, 5, 6, 8, 9, 10, 12, 16], "experimenting": 1, "littl": [1, 5, 6], "strongly": [1, 5, 11, 13], "integrated": 1, "programming": [1, 6, 8, 15], "experienc": [1, 12], "allowing": [1, 11], "checking": [1, 11, 12, 14], "linting": 1, "visual": 1, "debugging": [1, 4], "nowadays": 1, "two": [1, 2, 4, 5, 6, 8, 11, 13, 14, 15], "mult": [1, 2, 6, 14], "main": [1, 2, 3, 4, 6, 8, 13, 14, 15], "choic": [1, 11, 13], "studi": 1, "cod": [1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "aka": 1, "jetbrains": 1, "quand": [1, 4, 6, 8, 13], "vai": [1, 4, 6, 8], "lid": [1, 4, 5, 6, 13], "complex": [1, 2, 4, 5, 6, 8, 9, 12, 14, 15], "confiabil": 1, "necess": [1, 2, 4, 5, 6, 8], "suger": [1, 6], "usar": [1, 2, 4, 5, 6, 8, 9, 13], "ambient": [1, 2], "virtu": 1, "cham": [1, 2, 4, 5, 7, 8, 10, 13], "virtualenv": [1, 2], "vej": [1, 2, 3], "aqu": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], "docs": [1, 2, 4], "org": [1, 2, 4, 7, 9, 15], "7": [1, 2, 7, 8, 11, 14], "venv": [1, 2], "__": [1, 2, 4], "introdu": [1, 2], "Em": [1, 2, 5, 6, 8, 9], "confus": [1, 6], "git": [1, 2], "keep": [1, 2, 4, 5, 6, 7, 8, 9, 11, 14], "track": [1, 14], "progr": [1, 2, 3, 9], "chang": [1, 2, 3, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16], "sav": [1, 5, 6, 10, 11], "saf": [1, 6, 11, 13, 14], "plac": [1, 5, 6, 8, 11, 13, 15], "gitlat": 1, "bitbucket": 1, "editor": [1, 8], "highlighting": [1, 8], "highly": [1, 13], "recommend": [1, 5], "quit": [1, 5, 7, 8, 11, 13], "run": [1, 3, 4, 6, 8, 12], "debug": [1, 2, 5, 6, 8], "just": [1, 2, 4, 5, 6, 7, 8, 11, 13, 15], "fold": [1, 2, 3, 4, 5, 6, 7, 10, 11, 13, 14], "add": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "args": [1, 2, 6], "your_full_path_to_py4web": 1, "py": [1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15], "launch": [1, 2], "configuration": 1, "windows": [1, 2, 4, 6], "paramet": [1, 2, 4, 5, 6, 12, 13, 15], "must": [1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 14], "forward": 1, "slash": [1, 4, 5, 12], "only": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "c": [1, 2, 3, 5, 6, 8, 9, 11, 14], "your_nam": [1, 15], "instead": [1, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "copy": [1, 3, 4, 6, 13], "standard": [1, 2, 3, 6, 7, 12, 13, 14, 15, 16], "insid": [1, 2, 4, 5, 6, 8, 9, 11, 13, 14, 15], "renam": 1, "order": [1, 2, 4, 5, 6, 7, 9, 11, 13, 14], "avoid": [1, 2, 5, 6, 11], "errors": [1, 5, 6, 7, 11, 14], "lat": [1, 2, 4, 5, 6, 8, 9, 11, 13], "usr": 1, "bin": [1, 2], "env": [1, 14], "python3": [1, 2], "cor": [1, 2, 4, 5, 11], "cli": [1, 2], "both": [1, 5, 6, 8, 11, 14], "should": [1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "get": [1, 2, 4, 5, 6, 8, 11, 12, 13, 14, 15, 16], "gevent": [1, 2], "tru": [1, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "enabl": [1, 5, 7, 9, 12, 14, 15], "settings": [1, 4, 5, 6, 9, 12, 14], "build": [1, 2, 4, 5, 9, 13, 15], "execution": 1, "deployment": 1, "debugg": 1, "help": [1, 2, 5, 6, 7, 8, 9, 11, 13, 14], "support": [1, 2, 6, 11, 14], "efforts": 1, "participat": 1, "group": [1, 3, 5, 12, 14], "trying": [1, 12], "answer": 1, "submit": [1, 5, 6, 9, 11, 13], "bugs": 1, "pull": 1, "requests": [1, 4, 5, 14], "repository": [1, 2, 3], "Se": [1, 2, 3, 4, 6, 8, 9, 13], "desej": [1, 2, 5, 6, 12, 13], "corrig": 1, "ampli": 1, "traduz": [1, 6, 8, 10], "l\u00edngu": 1, "estrangeir": [1, 13], "ler": [1, 6], "tod": [1, 2, 3, 4, 5, 7, 8, 9, 10, 12, 13, 15], "inform": [1, 4, 5, 6, 11], "necess\u00e1r": [1, 5, 6], "diret": [1, 5, 6, 8], "readm": [1, 4, 11], "espec\u00edf": [1, 2, 6], "blob": [1, 6], "mast": [1, 2, 13], "md": [1, 4], "really": [1, 4], "rst": 1, "doc": 1, "brows": [1, 2, 3, 5, 7, 8, 13, 15], "once": [1, 2, 5, 6, 11, 12, 15], "pr": 1, "accepted": [1, 6, 11, 14, 15], "branch": [1, 2], "reflected": 1, "pag": [1, 3, 4, 5, 6, 9, 11, 12, 13, 14, 15, 16], "epub": 1, "next": [1, 5, 6, 8, 11], "output": [1, 5, 8, 11, 15], "generation": 1, "befor": [2, 5, 6, 8, 11, 12, 13], "everything": [2, 4, 14], "else": [2, 4, 6, 11, 12, 13, 14, 15], "imported": [2, 11, 12, 14], "charg": [2, 14], "starting": [2, 4, 6, 11], "reason": [2, 5, 6, 13, 14], "things": [2, 5, 6], "py4web": [2, 3, 4, 5, 7, 8, 9, 11, 12, 13], "download": 2, "pypi": 2, "github": [2, 3, 4, 6, 13], "folders": 2, "collections": 2, "want": [2, 4, 5, 6, 8, 11, 15], "command": [2, 4, 6, 8], "lin": [2, 4, 5, 7, 8, 9, 11, 12, 13, 15], "options": [2, 6, 7, 9, 12, 14, 15], "initializ": 2, "existing": [2, 5, 6, 11], "scaffolding": [2, 4, 5, 6, 8, 14, 15], "under": [2, 3, 4, 5, 6, 11], "concurrently": [2, 11], "served": [2, 6], "process": [2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "address": [2, 11, 12], "each": [2, 5, 6, 7, 8, 9, 11, 12, 13, 14], "runs": [2, 4, 12, 14], "fin": [2, 12, 13], "mac": [2, 6], "linux": 2, "prerequisit": [2, 5], "advanc": [2, 12, 13], "except": [2, 5, 6, 8, 9, 11, 12, 15], "binari": 2, "quatr": 2, "altern": [2, 6], "diferent": [2, 5, 6, 10, 13], "n\u00edv": 2, "dificuldad": 2, "olhar": 2, "pr\u00f3s": 2, "contr": [2, 6], "real": [2, 4, 7, 8, 11], "porqu": [2, 4, 5, 6, 8, 10], "acab": [2, 6], "copi": 2, "mont": [2, 12], "modific": [2, 4], "maneir": [2, 5, 6, 13], "da\u00ed": 2, "especial": [2, 4, 6, 10], "inic": [2, 4], "alun": 2, "direit": [2, 3, 6], "Por": [2, 3, 4, 5, 6, 9], "experimental": [2, 5], "cont": [2, 5, 9, 10, 12], "liber": 2, "idad": 2, "adicion": [2, 5, 10, 11, 12, 13], "us\u00e1": [2, 6, 8], "faz": [2, 5, 8, 9, 11, 12], "recent": [2, 3, 6, 11, 13], "reposit\u00f3ri": 2, "extern": 2, "nicozanf": 2, "pyinstall": 2, "descompact": 2, "past": [2, 4], "abrir": 2, "l\u00e1": [2, 4], "Com": [2, 6], "tip": [2, 4, 13], "lembr": [2, 3, 6, 8, 11], "sempr": [2, 6, 8], "vez": [2, 4, 5, 8, 9, 12, 13], "seguint": [2, 4, 5, 6, 8, 9, 10, 11, 12, 13], "document": [2, 6, 8, 9, 11, 15], "notic": [2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "correspond": [2, 4, 6, 10], "latest": [2, 6, 14], "stabl": 2, "although": [2, 8, 11], "best": [2, 5, 6], "up": [2, 5, 6, 8, 12, 15], "installation": 2, "procedur": 2, "quickly": [2, 11, 13], "install": [2, 6, 12], "releas": 2, "upgrad": [2, 6], "dir": [2, 11, 15], "user": [2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "try": [2, 5, 6, 8, 11, 12], "specify": [2, 4, 5, 6, 7, 11, 12, 13], "full": [2, 4, 6, 8, 11, 15], "8": [2, 6, 7, 11], "ir\u00e1": [2, 3, 4, 6, 8, 12, 13], "dependent": [2, 3, 4, 5], "caminh": [2, 4, 6, 13], "ativ": [2, 6], "cont\u00e9m": [2, 4, 5, 6], "cri": [2, 3, 5, 6, 8, 9, 12, 13, 16], "ap\u00f3s": [2, 6, 13], "trabalh": [2, 4, 6, 7], "aceit": [2, 6, 10, 11], "signif": [2, 5, 6, 7], "No": [2, 5, 6, 8, 13], "exe": 2, "apont": [2, 6], "digit": [2, 11, 12], "engan": 2, "indesej": [2, 6], "bom": 2, "h\u00e1bit": 2, "Este": [2, 6, 8, 9, 13, 14], "recurs": [2, 8, 16], "aind": [2, 6, 12], "moment": [2, 6], "descobert": [2, 10], "instructions": [2, 11], "activating": 2, "activat": [2, 12], "without": [2, 6, 8, 9, 15, 16], "traditional": 2, "way": [2, 3, 5, 6, 8, 9, 11, 12, 14], "works": [2, 8, 11, 12, 13, 14, 15], "normally": [2, 6, 8, 9, 11, 13], "utility": [2, 11], "path": [2, 4, 5, 6, 7, 10, 11, 13, 14, 15], "along": [2, 4, 9, 11, 15], "links": [2, 6, 13, 15], "clon": [2, 4, 6, 15], "cd": 2, "assets": 2, "test": [2, 6, 8, 9, 11, 13], "content": [2, 4, 5, 6, 8, 9, 11, 13, 14, 15], "missing": [2, 12, 13], "manually": [2, 4, 6, 9, 12, 13], "upgraded": 2, "itself": [2, 6, 11, 15], "copied": 2, "useful": [2, 4, 8, 9, 11, 13, 15], "already": [2, 4, 5, 9, 11, 12, 13], "working": [2, 11], "locally": 2, "globally": [2, 6], "present": [2, 6, 8, 11, 12], "gain": 2, "potentially": 2, "untested": [2, 12], "go": [2, 4, 13], "given": [2, 5, 6, 8, 11], "then": [2, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "r": [2, 6, 11], "tiv": [2, 6], "tant": [2, 6, 15], "observ": [2, 4, 6, 8], "forc": [2, 6, 7, 11, 12], "praz": 2, "programs": [2, 11], "executed": [2, 5, 6, 8, 14, 15], "ones": [2, 7, 11, 14], "don": [2, 4, 6, 8, 11, 12, 13, 14], "directly": [2, 4, 6, 7, 9, 11, 12, 13, 15], "usual": [2, 8, 9, 11], "atualiz": [2, 16], "automat": [2, 4, 6, 8, 9, 12, 13], "dashboard": [2, 4, 6, 8, 11, 16], "remov": [2, 4, 6, 9, 11, 12], "manual": [2, 3, 4, 6], "los": [2, 4, 6, 11], "precau\u00e7\u00e3": 2, "seguranc": [2, 5, 6, 9], "fez": [2, 6], "any": [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "backup": [2, 6, 11], "personal": 2, "ve": [2, 3, 5, 8, 9, 11, 13, 15], "delet": [2, 4, 7, 11, 13, 15], "old": [2, 5, 11, 14], "again": [2, 4, 5, 6, 11, 15], "utiliz": [2, 3, 4, 5, 6, 9, 12, 13], "anterior": [2, 6], "produz": [2, 5, 6, 8, 9], "sa\u00edd": [2, 4, 6, 8, 9], "generally": [2, 5, 8], "nam": [2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "wit": 2, "nothing": [2, 5, 6, 11, 15], "prevents": [2, 5, 15], "grouping": 2, "expects": [2, 9], "_dashboard": [2, 3], "default": [2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15], "_default": [2, 4], "installs": 2, "Ele": [2, 3, 5, 6, 9], "descrit": [2, 4, 6, 13], "pr\u00f3xim": [2, 6], "cap\u00edtul": [2, 4, 5, 14], "nad": [2, 4, 6], "boas": 2, "vind": 2, "t\u00eam": [2, 4, 6, 12], "papel": 2, "portant": [2, 4, 5, 6, 9], "nom": [2, 4, 5, 7, 9, 11, 12, 13], "_": [2, 11, 15], "Uma": [2, 4, 6, 8, 9], "send": [2, 6, 11, 12], "urls": [2, 11, 14], "localhost": [2, 4, 5, 6, 11], "8000": [2, 3, 4, 11, 13], "yourappnam": 2, "stop": 2, "acert": 2, "kbd": 2, "control": [2, 8, 11, 12, 13, 15], "janel": 2, "onde": [2, 6, 7, 8, 11], "soment": [2, 4, 6, 8], "appnam": [2, 5, 12, 14], "prefix": [2, 4, 6, 7, 8, 11, 14], "quer": [2, 4, 5, 6, 9], "lig": [2, 5, 6, 9], "simbol": 2, "trailing": [2, 11], "optional": [2, 4, 5, 6, 7, 8, 11, 12, 13, 14], "ctrl": [2, 3], "break": [2, 6], "fn": 2, "paus": 2, "v\u00e1r": [2, 4, 5, 6], "argument": [2, 4, 5, 6, 8, 9, 11, 12], "ter": [2, 4, 5, 6, 9], "adicional": [2, 5, 6], "h": [2, 11], "usag": [2, 3, 4, 5, 6, 9, 11, 12, 13], "apps_fold": 2, "func": [2, 5], "function": [2, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15], "y": [2, 9, 11], "yes": [2, 6], "prompt": [2, 4, 6], "assum": [2, 5, 6, 7, 10], "fals": [2, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "text": [2, 5, 6, 8, 9, 13, 15], "arguments": [2, 5, 6, 8, 9, 11, 12, 14], "passed": [2, 4, 5, 6, 9, 11, 12, 13, 15], "show": [2, 5, 6], "messag": [2, 4, 5, 7, 8, 11, 13, 14, 15], "exit": 2, "myfunction": 2, "x": [2, 6, 8, 9, 11, 13], "100": [2, 6, 7, 8, 11], "singl": [2, 5, 6, 7, 8, 11, 12, 13], "doubl": [2, 6, 8], "quot": [2, 9], "shown": [2, 6, 11, 12], "parameters": [2, 6], "app_nam": [2, 4, 5, 6], "copying": [2, 8, 9, 16], "scaffold_zip": 2, "erro": [2, 3, 6], "orig": [2, 10], "host": [2, 5, 11, 12], "127": [2, 3, 5, 11, 13], "0": [2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "1": [2, 3, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15], "p": [2, 8, 11], "integ": [2, 6, 7, 9, 11, 15], "numb": [2, 5, 6, 8, 11], "password_fil": 2, "encrypted": [2, 5], "password": [2, 6, 8, 11, 12, 14, 15], "wsgiref": 2, "gunicorn": 2, "waitress": 2, "geventwebsocketserv": 2, "wsgirefthreadingserv": 2, "rocketserv": 2, "w": [2, 4, 5, 6], "number_workers": 2, "workers": 2, "d": [2, 7, 9, 11], "dashboard_mod": 2, "mod": [2, 4, 5, 6, 11, 12], "readonly": [2, 11, 15], "non": [2, 4, 5, 6, 7, 9, 11, 13, 14, 15], "watch": [2, 3, 16], "sync": [2, 6], "lazy": [2, 4, 6], "reload": [2, 3, 4, 5, 11], "automatically": [2, 3, 4, 5, 6, 11, 12, 13, 14, 15], "ssl_cert": 2, "ssl": 2, "certificat": 2, "ssl_key": 2, "key": [2, 4, 5, 6, 7, 9, 11, 12, 16], "errorlog": 2, "error": [2, 3, 5, 6, 7, 8, 9, 11, 15], "logs": [2, 5, 6], "stdout": 2, "stderr": 2, "tickets_only": 2, "filenam": [2, 4, 5, 6, 11], "l": [2, 11], "logging_level": 2, "level": [2, 6, 7, 11, 12], "50": [2, 7], "30": [2, 6, 8, 11], "warning": [2, 5], "switch": [2, 5, 6], "application": [2, 5, 6, 8, 12, 14, 15], "upon": [2, 12, 14], "reloading": [2, 4, 5], "occur": [2, 11, 15], "incoming": 2, "changed": [2, 3, 4, 5, 6, 8, 11, 14, 15], "pref": [2, 15], "immediat": 2, "production": [2, 4], "servers": [2, 6], "unneded": 2, "checks": [2, 6, 8, 11, 12], "restart": [2, 4, 5, 6, 13], "directiv": [2, 8, 11], "looks": [2, 6, 11], "occurring": 2, "modifications": 2, "requir": [2, 5, 6, 11, 12], "used": [2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "currently": [2, 5, 8], "behaviour": [2, 11, 13], "option": [2, 3, 5, 6, 11, 12, 15], "rocket3": [2, 14], "threaded": [2, 14], "stripped": [2, 14], "python2": [2, 14], "logic": [2, 4, 5, 12, 14], "valu": [2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15], "defined": [2, 4, 5, 6, 8, 11, 12, 13, 14, 15], "logging": [2, 12], "corresponds": [2, 6, 9], "common": [2, 4, 5, 8, 11, 12, 14, 15], "notset": 2, "10": [2, 6, 7, 8, 9, 10, 11, 12, 15], "20": [2, 7, 10, 11], "info": [2, 4, 5, 12], "40": [2, 9, 15], "critical": 2, "telling": [2, 12], "library": [2, 6, 10, 11, 14, 15], "handl": [2, 4, 5, 6, 14, 15], "events": [2, 15], "sets": [2, 5, 6, 11], "calls": [2, 6, 8, 15], "functions": [2, 5, 9, 15], "found": [2, 11], "invalid": [2, 6, 11], "saved": [2, 5, 6, 11], "administrator": 2, "asked": 2, "cad": [2, 4, 5, 8, 10, 13], "ped": [2, 4, 5, 6, 13], "uso": [2, 5, 13], "Isso": [2, 4, 6], "chat": 2, "pdkdf2": 2, "hash": [2, 6, 11], "vou": 2, "exclu\u00edd": [2, 6], "my_password_fil": 2, "depo": [2, 3, 6], "reutiliz": [2, 6], "temp": [2, 8], "execu": [2, 4], "validators": [2, 14], "crypt": 2, "writ": [2, 4, 5, 6, 8], "str": [2, 5, 6, 9, 14, 15], "input": [2, 5, 6, 8, 11, 15], "reinstall": 2, "reinstal": 2, "necess\u00e1ri": [2, 4, 5, 6], "confirm": [2, 11, 13], "cria\u00e7\u00e3": [2, 6, 13], "segu": [2, 4, 5, 6, 8], "atual": [2, 6], "existent": [2, 6, 12, 13], "parent": [2, 6, 8, 15], "O": [2, 8, 9, 11, 12, 16], "apen": [2, 5, 6, 7], "pesquis": [2, 3, 6, 13], "ent\u00e3": [2, 4, 6, 12], "exempl": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "conch": 2, "pud": 2, "myapp": [2, 4], "db": [2, 3, 4, 5, 7, 9, 11, 12, 13, 14, 15], "translator": [2, 10, 16], "dal": [2, 4, 7, 11, 13, 15, 16], "field": [2, 4, 5, 7, 12, 13, 14, 15, 16], "utils": [2, 4, 5, 8, 9, 11, 12, 13, 14, 16], "versions": [2, 5, 11], "too": [2, 5, 6, 11, 15], "cannot": [2, 5, 6, 8, 11, 15], "generic": [2, 4, 5, 6, 11], "described": [2, 4, 5, 6, 11], "called": [2, 5, 6, 7, 8, 9, 11, 13, 14, 15], "deployment_tools": 2, "collects": 2, "recip": 2, "they": [2, 4, 5, 6, 7, 8, 11, 12, 14, 15], "briefly": 2, "tips": [2, 13], "tricks": 2, "thes": [2, 4, 5, 6, 8, 9, 11, 13, 15], "steps": 2, "generat": [2, 6, 8, 9, 11, 12, 13, 14, 15], "followed": [2, 5, 11], "www": [2, 8, 9, 13], "section": [2, 4, 6, 8, 11, 12], "io": [2, 6], "engineering": 2, "education": 2, "securely": 2, "vscod": 2, "may": [2, 4, 5, 6, 8, 9, 11, 14, 15], "updat": [2, 5, 11, 13, 14, 15], "contain": [2, 4, 5, 6, 8, 9, 11, 12, 15], "configurations": 2, "type": [2, 5, 7, 8, 9, 12, 15], "workspacefold": 2, "path_t": 2, "crt": [2, 6], "absolut": [2, 6, 14], "location": [2, 4, 6, 8, 14], "so": [2, 5, 6, 8, 9, 11, 13, 14, 15], "feasibl": [2, 11], "simply": [2, 4, 5, 6, 8, 15], "py4web_wsg": 2, "4": [2, 5, 6, 7, 8, 11, 13], "tak": [2, 3, 5, 6, 8, 11, 12, 13, 15], "consol": [2, 9, 12, 15], "obtain": [2, 12, 15], "id": [2, 5, 7, 8, 9, 11, 12, 13, 14, 15], "project_nam": 2, "mkdir": [2, 4, 7, 11, 13], "supond": [2, 6], "cp": 2, "development_tools": 2, "talvez": [2, 5], "__init": 2, "vazi": [2, 6], "ver": [2, 6, 12], "makefil": 2, "__init__": [2, 4, 5, 6, 7, 11, 13, 14, 15], "lib": 2, "yaml": 2, "sdk": 2, "config": [2, 4], "email": [2, 8, 11, 12, 14], "mail": [2, 3, 5, 11, 12], "obtid": [2, 6], "agor": [2, 4, 5, 6, 8], "bast": 2, "deploy": 2, "atend": [2, 6], "youtub": [2, 6], "follow": [2, 4, 7, 12, 13, 14], "detailed": [2, 13], "bottle_app": 2, "script": [2, 5, 8, 15], "dockerfil": 2, "compos": 2, "yml": 2, "setting": [2, 4, 5, 6, 11, 13], "postgresql": [2, 6], "advantag": [2, 6, 8, 15], "requiring": [2, 14], "sud": [2, 12], "background": [2, 4, 11, 15], "daemon": 2, "bash": 2, "04": 2, "03": [2, 6, 7], "lts": 2, "nginx": 2, "self": [2, 5, 6, 9, 11, 13, 15], "signed": [2, 5, 11], "manag": [2, 3, 4, 5, 6, 12], "iptabl": 2, "surely": [3, 4], "extensively": 3, "check": [3, 4, 5, 6, 11, 12, 13], "looking": 3, "good": [3, 11], "exploring": 3, "listening": 3, "tcp": 3, "local": [3, 5, 8, 11, 12, 13], "pc": 3, "protocol": 3, "connect": [3, 6], "firefox": [3, 15], "chrom": [3, 15], "bot\u00f5": [3, 16], "describ": [3, 11], "chapt": [3, 4, 5, 6, 8, 9, 11, 13], "documentation": [3, 11, 15], "20201112": 3, "browsing": 3, "pointing": [3, 8], "discuss": 3, "forum": 3, "pression": 3, "bot\u00e3": 3, "transmit": 3, "inser": [3, 8], "ref": [3, 5, 6, 9, 11, 16], "comando": [3, 4, 6, 9, 16], "set_password": 3, "configur": [3, 12, 13, 14, 16], "exib": [3, 6, 8, 9, 13], "abas": 3, "comprim": 3, "cliqu": [3, 13], "t\u00edtul": [3, 9, 13], "gui": 3, "expand": 3, "As": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15], "context": [3, 5, 6, 8], "aba": 3, "instal": [3, 6, 12, 16], "selecion": [3, 6], "rot": [3, 13], "tab": [3, 5, 11], "allows": [3, 4, 5, 6, 7, 8, 11, 12, 13, 15], "contains": [3, 5, 7, 8, 10, 11, 13, 14, 15], "selected": [3, 4, 7, 9, 11, 15], "compris": 3, "reloaded": [3, 4, 15], "unless": [3, 5, 6, 8, 11], "op\u00e7\u00e3": [3, 4, 6, 8, 9], "click": [3, 9, 11, 13, 15], "effect": [3, 6, 11], "fails": [3, 11], "load": [3, 5, 15], "corresponding": [3, 6, 11, 12, 13, 14], "button": [3, 4, 5, 11, 13, 15], "displayed": [3, 6, 9, 11, 13, 15], "realiz": [3, 4, 5, 6], "busc": [3, 6, 13], "crud": [3, 11, 15], "visit": [3, 5, 11], "desencad": 3, "emit": [3, 6], "registr": [3, 4, 11, 12, 13], "edi\u00e7\u00f5": 3, "comuns": 3, "filesyst": [4, 5, 6], "going": [4, 15], "own": [4, 5, 6, 8, 11, 12, 13, 14, 15], "mind": [4, 6, 7, 8, 11], "therefor": [4, 5, 8, 11, 14], "empty": [4, 11, 15], "strictly": [4, 15], "needed": [4, 5, 6, 9, 11, 12, 15], "enter": [4, 11, 12], "commands": [4, 6, 8, 9], "echo": 4, "backslash": 4, "i": [4, 5, 6, 7, 8, 11, 12, 15], "press": [4, 13], "recogniz": 4, "automatic": [4, 6, 8, 14], "whenev": 4, "required": [4, 5, 6, 7, 11, 15], "anything": 4, "arbitrary": [4, 12], "access": [4, 5, 9, 12, 14, 15], "typically": [4, 8], "expos": [4, 5], "dynamic": [4, 8, 12], "expor": [4, 12], "simples": [4, 6, 10], "subpast": 4, "public": 4, "hell": [4, 5, 6, 8, 9, 11, 12, 15], "world": [4, 5, 6, 8, 9, 11], "rec\u00e9m": [4, 6, 10], "sob": [4, 6], "internally": [4, 5, 6, 13], "supports": [4, 5, 8, 9, 11, 15], "streaming": [4, 14], "partial": [4, 6, 14], "rang": [4, 6, 8, 12, 14], "modified": [4, 11, 14], "handled": [4, 5, 11, 15], "headers": [4, 8, 15], "fun\u00e7\u00e3": [4, 6, 8, 9], "conte\u00fad": [4, 6, 9], "novaaplicaca": 4, "init": [4, 13], "seg": 4, "datetim": [4, 5, 6, 7, 11, 15], "action": [4, 5, 6, 7, 9, 11, 12, 13, 14, 15], "def": [4, 5, 6, 7, 9, 11, 12, 13, 14, 15], "return": [4, 5, 6, 7, 9, 11, 12, 13, 15], "accessibl": 4, "\u00edndic": 4, "opcional": [4, 6, 9, 11], "Ao": [4, 6, 8, 13], "estrutur": [4, 5, 6, 10], "c\u00f3dig": [4, 5, 6, 8, 9], "ocorr": [4, 6, 8], "v\u00e1ri": [4, 5, 6, 10], "fun\u00e7\u00f5": [4, 8], "prepends": 4, "sej": [4, 6], "url": [4, 5, 7, 8, 11, 12, 13, 14, 15], "a\u00e7\u00e3": [4, 5, 6, 12, 16], "anteced": 4, "ambigu": [4, 6], "exce\u00e7\u00e3": [4, 5, 6, 8], "link": [4, 7, 8, 9, 11, 12, 13], "simb\u00f3l": 4, "actions": [4, 5, 6, 13, 14, 16], "string": [4, 5, 6, 7, 9, 10, 11, 13], "dictionary": [4, 5, 6, 9, 11, 14], "tell": [4, 12, 15], "what": [4, 5, 6, 8, 11, 12, 14, 15], "serializ": [4, 6, 8, 9], "end": [4, 6, 8, 11, 12, 13, 15], "colors": [4, 6], "blu": [4, 6, 11, 14], "green": [4, 6, 14], "vis\u00edvel": 4, "vermelh": 4, "azul": 4, "verd": 4, "conven\u00e7\u00e3": 4, "transform": [4, 5, 6, 11, 15], "inteir": [4, 6], "tard": [4, 6], "irem": [4, 5], "brev": 4, "poss\u00edvel": [4, 6], "map": [4, 6, 9, 10], "padr\u00f5": [4, 6, 9], "color": [4, 6, 8, 9, 11, 15], "picked": 4, "unknown": 4, "sintax": [4, 6, 9, 12, 16], "garraf": 4, "bottlepy": [4, 5], "wildcard": 4, "filt": [4, 7, 11, 13], "possibl": [4, 5, 6, 8, 11, 13, 14], "filters": [4, 11], "int": [4, 6, 11, 14], "d\u00edgit": [4, 6], "assinatur": [4, 5], "convert": [4, 6, 9], "n\u00famer": [4, 6, 10, 13], "float": [4, 11], "decim": 4, "personagens": [4, 6], "caracter": 4, "barr": 4, "gananc": 4, "combin": [4, 5, 9, 13], "segment": 4, "exp": [4, 6], "expression": [4, 6, 8, 10, 11, 13], "matched": [4, 7, 9, 11], "harmoniz": 4, "car\u00e1ct": [4, 6], "universal": [4, 6, 9], "pass": [4, 5, 6, 8, 9, 11, 12, 13, 14, 15], "vari\u00e1vel": [4, 6, 9, 10], "especific": [4, 5, 6], "al\u00e9m": [4, 6, 13], "diss": [4, 6, 13], "ac\u00e7\u00e3": [4, 5, 6, 7], "method": [4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15], "m\u00e9tod": [4, 5, 6, 8, 13], "post": [4, 6, 7, 9, 11, 12, 13, 14, 15], "paint": [4, 11], "query": [4, 5, 7, 9, 11, 13, 14, 15], "painting": 4, "equivalent": [4, 5, 6, 9, 11, 14], "additional": [4, 5, 7, 8, 11, 13], "attribut": [4, 5, 6, 9, 11, 13, 14, 15], "identify": 4, "decl": [4, 6], "head": [4, 5, 6, 8, 13, 15], "style": [4, 6, 8, 11, 15, 16], "body": [4, 8, 12, 13, 15], "h1": [4, 8], "tint": 4, "dict": [4, 5, 6, 10, 11, 13, 14, 15], "fund": 4, "correspondent": [4, 6, 9], "ingredient": 4, "chav": [4, 8, 9, 10, 13], "lumin\u00e1r": 4, "disposit": [4, 5, 12], "el\u00e9tr": [4, 5, 12], "comport": [4, 6, 9, 13], "inicializ": [4, 5], "filtrag": [4, 13], "entrad": [4, 6, 9, 10], "depend": [4, 5, 6], "\u00e2mbit": 4, "encaix": 4, "\u00e1rvor": 4, "explic": [4, 6], "acess\u00f3ri": 4, "especif": [4, 6], "dand": 4, "Esse": 4, "estar": [4, 6, 9], "localiz": 4, "diret\u00f3ri": 4, "ser\u00e3": [4, 6, 9], "cord": [4, 6, 10], "facil": [4, 5, 6], "linguagens": 4, "diz": [4, 6, 12, 13], "obter": [4, 6], "conex\u00e3": 4, "part": [4, 9, 14], "piscin": [4, 6], "compromet": [4, 6], "sucess": [4, 6], "revers\u00e3": 4, "falh": 4, "sess\u00e3": [4, 16], "analis": [4, 6, 10], "recuper": [4, 6], "salv": [4, 6, 10], "tradutor": 4, "cabe\u00e7alh": [4, 9], "accept": [4, 5, 11, 15], "determin": [4, 5, 6, 10, 11, 12, 13], "\u00f3ptim": 4, "regr": [4, 5], "liga\u00e7\u00e3": [4, 5, 6], "ambos": [4, 5, 6], "trat": [4, 6], "writing": [4, 6, 14], "scratch": 4, "san": 4, "conventions": [4, 6], "outlined": [4, 5], "putting": 4, "organized": 4, "properly": 4, "pre": [4, 5, 13], "shows": [4, 8, 11, 12], "registration": [4, 11], "building": [4, 11, 15], "construction": 4, "giv": [4, 5, 6, 8, 11, 12], "kind": 4, "normal": [4, 5, 6, 8, 11, 13, 15], "encontr": [4, 6, 8, 10, 12], "andaim": 4, "la": [4, 6], "usand": [4, 5, 8, 9, 12, 16], "imports": 4, "controllers": [4, 6, 8, 9, 11, 14, 15], "metadat": [4, 6], "models": [4, 6, 7, 13, 14], "tabl": [4, 5, 7, 11, 12, 13, 14, 15, 16], "settings_privat": 4, "privat": [4, 5, 11], "ship": 4, "bulm": [4, 11, 13, 15], "agnostic": 4, "favicon": 4, "ico": 4, "replac": [4, 6, 8, 9, 13, 15], "tasks": [4, 6], "etc": [4, 5, 6, 7, 10, 14], "general": [4, 5, 6, 11, 12], "layout": [4, 11, 12, 13, 14, 15, 16], "translations": [4, 5, 10, 15], "internationalization": [4, 5, 11, 14], "pluralization": [4, 14], "italian": [4, 5, 10], "respons": [4, 5, 6, 8, 14, 15, 16], "abort": [4, 6], "redirect": [4, 5, 11, 12, 13, 15], "helpers": [4, 6, 8, 11, 13, 14, 16], "welcom": [4, 5, 8, 14, 15], "get_us": [4, 5, 12, 14], "first_nam": [4, 5, 12, 13], "format": [4, 5, 7, 9, 10, 12, 13], "hom\u00f3log": 4, "div": [4, 5, 8, 11, 14, 15], "span": [4, 14], "img": 4, "indic": [4, 6], "esper": [4, 5, 6], "v\u00e1l": 4, "redirecion": [4, 12, 13], "provavel": [4, 5, 6], "mud": [4, 5, 6], "agn\u00f3st": 4, "algum": [4, 6, 9], "exce\u00e7\u00f5": 4, "quis": [4, 6], "scaffold": [4, 15], "lot": [4, 6], "point": [4, 5, 6, 11, 14, 15], "testing": [4, 6], "developing": 4, "whol": [4, 11], "anoth": [4, 5, 6, 8, 11], "my_app": 4, "loaded": [4, 8, 13, 15], "select": [4, 7, 10, 11, 12, 13, 14, 15, 16], "upload": [4, 6], "upper": [4, 5, 11], "finally": [4, 13], "facilitat": 4, "reloads": [4, 5], "fact": [4, 5, 14], "watched": 4, "app_watch_handl": 4, "decorator": [4, 5, 6, 14], "reported": 4, "worry": 4, "fully": [4, 5, 6, 11], "assist": 4, "sass": 4, "compil": [4, 8, 9, 11], "libsass": 4, "static_dev": 4, "overrid": [4, 6, 8, 12, 13, 14, 15], "sass_compil": 4, "changed_fil": 4, "print": [4, 6, 9, 10, 12, 14], "abov": [4, 5, 6, 7, 8, 11, 13], "compiled_css": 4, "filep": 4, "include_paths": 4, "includ": [4, 6, 7, 9, 13, 15], "output_styl": 4, "compressed": 4, "dest": [4, 6, 11], "join": [4, 5, 7, 11, 13, 14], "compiled": [4, 8, 9, 11], "valid": [4, 6, 9, 14, 16], "esprim": 4, "implementation": [4, 8, 11, 14], "nod": 4, "dbadmin": 4, "validate_js": 4, "cf": 4, "validation": [4, 7, 8, 13], "abspath": 4, "parsemodul": 4, "filepaths": 4, "relativ": [4, 14], "ignored": [4, 8, 9, 13, 15], "exceptions": [4, 5, 15], "handlers": 4, "printed": 4, "terminal": 4, "Um": 5, "pec": [5, 6], "equip": 5, "mobili\u00e1ri": 5, "fix": [5, 6], "posi\u00e7\u00e3": [5, 9], "edif\u00edci": 5, "ve\u00edcul": 5, "algo": [5, 6, 7, 8], "respost": [5, 8], "processing": [5, 6, 8, 11, 15], "operations": [5, 6], "perform": [5, 6, 11, 12], "pars": [5, 6, 11, 12], "look": [5, 6, 7, 11, 12], "information": [5, 6, 7, 11, 12, 14, 16], "commit": [5, 7, 13], "transaction": [5, 6], "preferred": [5, 6], "lookup": [5, 7], "prop": [5, 6, 13], "pick": [5, 12], "connection": [5, 6, 11], "pool": 5, "after": [5, 6, 8, 11, 12, 13], "back": [5, 6, 11], "mecan": [5, 6, 8], "ignor": [5, 6, 8, 11, 13], "eficient": [5, 6], "reduz": 5, "clich": 5, "middlewar": 5, "wsgi": 5, "plug": 5, "excet": [5, 6], "a\u00e7\u00f5": 5, "uns": 5, "signing": 5, "explained": [5, 6, 11, 13], "connections": [5, 6, 14], "authentication": [5, 13, 16], "develop": [5, 6, 8, 12, 13, 14], "paragraph": [5, 9, 11, 13], "previous": [5, 8, 9, 11, 15], "decorators": [5, 14], "applied": [5, 11, 13, 15], "knows": 5, "depends": [5, 6, 14], "exception": [5, 6, 11, 12], "otherwis": [5, 6, 8, 11, 12, 13], "various": 5, "delimiters": [5, 14], "retorn": [5, 8, 9], "posterior": 5, "jinja2": 5, "comum": [5, 6], "pouc": [5, 6], "a\u00e7\u00fac": 5, "sint\u00e1t": 5, "duas": [5, 6], "linh": [5, 6, 8, 9, 13, 16], "cached": 5, "ram": [5, 6], "right": [5, 6, 8], "careful": [5, 6, 8, 14, 15], "documentations": 5, "exactly": 5, "opposit": 5, "early": 5, "february": 5, "2022": 5, "extend": [5, 6, 12, 14, 15], "passing": [5, 6, 9], "implications": 5, "avoiding": [5, 11], "clean": [5, 9], "able": [5, 6, 11], "variabl": [5, 6, 9, 11, 13, 15], "my_var": [5, 9], "t_fold": 5, "dirnam": [5, 7, 11, 13], "__file__": [5, 7, 11, 13], "translated": [5, 8, 11], "specified": [5, 6, 8, 9, 11, 12, 13, 14, 15], "match": [5, 6, 8, 9, 10, 11], "class": [5, 6, 8, 9, 11, 12, 14, 15], "implement": [5, 6, 8, 12], "contador": [5, 6], "dbstor": 5, "memory": [5, 6], "storag": [5, 6, 7, 11, 13], "count": [5, 7, 12, 13, 15], "n": [5, 6, 10, 15], "tradu\u00e7\u00e3": [5, 16], "tradu\u00e7\u00f5": [5, 10], "en": [5, 7], "2": [5, 6, 7, 8, 9, 10, 11, 12, 14, 15], "twic": [5, 6], "6": [5, 6, 7, 11, 12, 14], "5": [5, 6, 7, 8, 10, 11, 12, 13, 15], "visiting": [5, 12], "preferenc": 5, "english": 5, "tent": [5, 6], "ti": 5, "ho": 5, "mai": 5, "vist": [5, 6, 9], "prim": [5, 11], "gia": 5, "volt": [5, 6], "piu": 5, "display": [5, 11, 12, 13, 15], "alerts": [5, 8, 14], "displaying": [5, 11, 13], "stat": [5, 7, 14, 15], "preserved": 5, "redirection": [5, 14], "dismissibl": 5, "auxili": [5, 6, 8, 9], "del": [5, 6, 9], "_class": [5, 9, 11, 15], "sanitiz": [5, 9, 14], "src": [5, 6, 8, 9, 15], "globals": [5, 6, 8, 12, 14], "xml": [5, 8, 11, 14, 15], "returned": [5, 6, 7, 8, 9, 11, 15], "triggers": 5, "position": 5, "convenienc": 5, "injected": [5, 8, 9, 11], "redirected": [5, 15], "remembered": 5, "achieved": [5, 14], "asking": 5, "temporarily": 5, "sent": [5, 6, 12, 15], "returning": 5, "overwritten": [5, 14], "mensagens": [5, 6], "defaults": [5, 11, 13, 14], "alert": [5, 8, 9, 11, 14], "success": [5, 7, 15], "hardcod": 5, "thos": [5, 6, 8, 9, 12, 14, 15], "basic": [5, 16], "speaking": 5, "desired": [5, 11], "persist": 5, "throughout": 5, "interaction": 5, "words": [5, 13], "rend": [5, 6, 11, 13, 14, 15], "stateless": [5, 15], "stateful": 5, "secret": [5, 12], "my": [5, 8, 9, 11, 12, 15], "increased": 5, "opening": 5, "updated": [5, 6, 11], "closing": [5, 6], "reopening": 5, "window": [5, 8, 11], "related": [5, 6], "usernam": [5, 6, 12], "visited": [5, 8], "shopping": 5, "cart": 5, "jwt": 5, "specifically": [5, 6, 11, 15], "token": [5, 11], "stor": [5, 6, 12], "serialized": [5, 6, 9, 11], "__str__": [5, 8, 9], "operator": [5, 6, 11], "lost": [5, 14], "composing": 5, "still": [5, 6, 9, 11, 13, 14], "minimal": [5, 13, 16], "identifying": [5, 11], "clients": 5, "sess\u00f5": 5, "nunc": [5, 6], "expir": 5, "contenh": 5, "hist\u00f3r": [5, 6], "par\u00e2metr": [5, 13], "expiration": 5, "3600": [5, 6], "algorithm": [5, 11], "hs256": 5, "same_sit": 5, "lax": 5, "_sesson": 5, "passphras": 5, "sign": [5, 8, 12], "maximum": [5, 11], "lifetim": 5, "seconds": [5, 11], "timeout": 5, "signatur": [5, 6, 7, 9, 11], "alternat": [5, 15], "csrf": [5, 11], "attacks": [5, 9], "cross": [5, 9], "forgery": 5, "enabled": [5, 7, 12, 14], "provided": [5, 6, 8, 9, 11, 13], "uuid": [5, 6], "why": [5, 8, 14], "appname_session": 5, "encoded": [5, 6, 11, 15], "preventing": 5, "tampering": [5, 6], "trivial": [5, 6], "communications": 5, "disk": [5, 6], "sensitiv": [5, 11], "invalidated": 5, "vic": 5, "vers": 5, "small": [5, 11], "siz": [5, 8, 11], "limit": [5, 6, 7, 11, 12], "kbytes": 5, "being": [5, 6, 7, 8, 11, 15], "put": [5, 7, 11, 15], "configured": [5, 11, 14], "conn": 5, "11211": 5, "6379": 5, "lambd": [5, 6, 9, 11, 13, 15], "k": [5, 8, 14], "v": [5, 9, 11, 15], "cs": 5, "ct": 5, "ttl": 5, "avis": [5, 6, 12], "rem": 5, "macac": 5, "multiprocess": 5, "quirk": 5, "deterministic": 5, "unsaf": [5, 9], "tud": [5, 6], "imagin": [5, 6, 8, 15], "fsstorag": 5, "exists": [5, 6, 11], "fp": 5, "dump": 5, "tmp": [5, 6], "leav": [5, 6], "exercis": 5, "per": [5, 7], "subfolders": [5, 6], "locking": 5, "storing": 5, "inefficient": 5, "scal": [5, 8], "app1": 5, "app2": 5, "wants": 5, "shar": [5, 6, 14], "assuming": 5, "sessons": 5, "session_secret_key": 5, "app1_session": 5, "tells": [5, 15], "sur": [5, 11, 15], "shared": 5, "between": [5, 9, 11, 14], "consistent": 5, "session_app1": 5, "restrict": [5, 7, 11, 15], "enforc": [5, 11], "workflow": [5, 16], "step1": 5, "step_completed": 5, "_href": [5, 8, 9, 11, 13], "step2": 5, "locals": [5, 14], "step3": 5, "on_request": 5, "evaluat": [5, 11, 15], "listed": [5, 11, 15], "rais": [5, 11, 14], "404": [5, 11], "cond": 5, "400": [5, 14], "raised": 5, "on_fals": 5, "13": [5, 6, 11], "giving": 5, "memberships": 5, "specific": [5, 6, 8, 9, 11, 12, 14, 15], "auth_us": [5, 6, 12], "requires_membership": 5, "group_nam": [5, 12], "user_id": [5, 6, 14], "payroll": 5, "employ": 5, "permission": [5, 12], "typical": [5, 6, 11], "follows": [5, 6, 11], "url_sign": 5, "somepath": 5, "controll": [5, 6, 8, 9, 11, 12, 13, 14, 15], "signs": 5, "signed_url": 5, "anotherpath": 5, "verify": 5, "verified": [5, 11], "usou": [5, 6], "queir": 5, "abstraction": [5, 14, 16], "lay": [5, 14, 16], "documented": [5, 11], "pleas": [5, 6], "rememb": [5, 6], "doesn": 5, "db_fold": [5, 7, 11, 13], "pool_siz": [5, 6], "define_tabl": [5, 7, 11, 12, 13, 14], "visit_log": 5, "client_ip": 5, "timestamp": [5, 7, 15], "environ": [5, 14], "remote_addr": [5, 12], "insert": [5, 7, 8, 11, 12, 13, 15], "utcnow": [5, 6], "picks": 5, "wrapped": 5, "commits": [5, 6], "on_success": 5, "rolls": 5, "on_error": 5, "rol": [5, 9], "construtor": [5, 9, 11, 16], "tabel": [5, 9, 12, 13, 14], "camp": [5, 7, 9, 11, 16], "last_nam": [5, 12, 13], "sso_id": [5, 12], "action_token": [5, 12], "\u00faltim": [5, 6, 12], "intern": [5, 6], "registers": 5, "including": [5, 11, 13, 14, 15], "presenc": [5, 6], "_scaffold": [5, 8, 9, 11, 12, 13, 14, 16], "returns": [5, 6, 7, 9, 11, 12, 14, 15], "logged": [5, 8, 12, 14], "redirects": [5, 12], "desd": [5, 6], "verific": [5, 6, 12], "plugin": [5, 11, 12, 13, 15], "methods": [5, 7, 9, 11, 12, 13, 16], "authorization": [5, 16], "compartilh": [5, 6], "permiss\u00e3": [5, 6, 12], "estad": [5, 6], "threads": [5, 6], "atribut": [5, 9], "thing": [5, 6, 14], "writabl": [5, 6, 11, 14], "readabl": [5, 6, 13, 14], "espec": [5, 6, 9], "threadsafevariabl": 5, "Esses": [5, 6], "parec": [5, 6], "rosc": 5, "loc": 5, "est\u00e3": [5, 6, 7, 9, 12], "valor": [5, 9, 10, 11, 13], "m\u00ednim": [5, 6], "myfixtur": 5, "transforms": 5, "determining": 5, "eventually": 5, "accessing": [5, 6], "inner": [5, 15], "layers": 5, "coming": 5, "calling": [5, 6, 8], "b": [5, 6, 8, 9, 11, 14, 15], "circumstanc": 5, "think": [5, 8, 13, 15], "onion": 5, "cent": [5, 6, 8], "entering": 5, "exiting": 5, "processed": [5, 6, 8, 11], "previously": [5, 6, 11], "current": [5, 6, 9, 12, 13, 14, 15], "uppercas": [5, 11], "upper_cas": 5, "tracebacks": 5, "logerrors": 5, "stre": [5, 6], "errlog": 5, "myerrors": 5, "__prerequisite__": 5, "appended": [5, 6], "__prerequisites__": 5, "guarant": 5, "singleton": [5, 14], "declared": 5, "considered": [5, 6, 8, 14], "httrespons": 5, "whil": [5, 6, 7, 11, 12, 14], "actual": [5, 6, 8, 12, 13], "complicated": 5, "individual": [5, 6, 7, 8, 11], "know": [5, 13], "wheth": [5, 6, 11, 12, 14], "keeps": [5, 13], "easy": [5, 8, 11], "communicat": 5, "retriev": [5, 6, 15], "stated": 5, "mandatory": [5, 8], "consid": [5, 8, 9, 11, 13], "happen": 5, "sequenc": [5, 6], "revers": [5, 6], "transformed": [5, 6], "extra": [5, 6, 11, 12], "almost": 5, "contexts": 5, "futur": [5, 14, 15], "implements": [5, 6], "recently": 5, "lru": 5, "via": [5, 6, 8, 9, 15], "1000": [5, 6, 11, 15], "60": [5, 6], "uuid4": [5, 6], "registered": [5, 12], "mention": 5, "rendered": [5, 7, 8, 11, 13, 14, 15], "unauthenticated": [5, 6, 9], "authenticated": [5, 6], "below": [5, 6, 7, 11, 13], "rout": [5, 13, 14, 15], "separated": [5, 6, 13, 15], "combined": [5, 6, 11], "preced": [5, 6, 8], "maps": [6, 9], "such": [6, 9, 11, 13, 14], "queri": [6, 7, 13, 14, 15], "records": [6, 7, 11, 12, 13], "dynamically": [6, 7, 8, 11], "dialect": 6, "dialects": 6, "term": 6, "generically": 6, "portabl": 6, "among": [6, 11], "choosen": 6, "pur": 6, "conceived": 6, "tast": 6, "transactions": 6, "aggregat": 6, "nested": [6, 8], "differenc": [6, 11, 14], "caveat": [6, 8, 14], "startup": [6, 14], "downsid": [6, 14], "approach": [6, 14], "nev": [6, 8, 11, 14, 15], "thread": [6, 14], "practical": [6, 11, 14, 16], "mailing": 6, "adapters": [6, 15], "modern": [6, 12, 15], "distribution": 6, "actually": [6, 8], "driv": 6, "sqlite3": 6, "included": [6, 8, 11, 13], "binary": 6, "appropriat": [6, 11], "drivers": 6, "pysqlite2": 6, "zxjdbc": 6, "jython": 6, "psycopg2": 6, "pymysql": 6, "mysqldb": 6, "cx_oracl": 6, "pyodbc": 6, "pypyodbc": 6, "firebird": 6, "kinterbasdb": 6, "fdb": 6, "db2": 6, "informix": 6, "informixdb": 6, "ingres": 6, "ingresdb": 6, "cubrid": 6, "cubriddb": 6, "sybas": 6, "teradat": 6, "sapdb": 6, "mongodb": 6, "pymong": 6, "imap": 6, "imaplib": 6, "treated": [6, 14], "gotch": 6, "about": [6, 7, 12, 13, 14, 15, 16], "comp\u00f5": 6, "instantiat": [6, 12, 14], "mytabl": 6, "myfield": 6, "truncat": 6, "import_from_csv_fil": 6, "instantiated": 6, "claus": 6, "myquery": 6, "myset": 6, "somevalu": 6, "something": [6, 7, 8, 9, 11], "derived": 6, "myord": 6, "advisabl": 6, "persistent": [6, 12], "hesitat": 6, "doing": [6, 11, 15], "snippets": 6, "executabl": [6, 9], "auth_user_tag_groups": [6, 12], "person": [6, 7, 11, 13], "superher": [6, 7, 11, 13, 15], "superpow": [6, 7], "product": [6, 11, 15], "superman": [6, 7, 11, 13], "real_identity": [6, 7], "zer": [6, 8, 11, 13], "sak": 6, "simplicity": [6, 15], "discussion": 6, "engin": [6, 11], "conect": [6, 12], "_ur": 6, "_dbnam": 6, "instanc": [6, 9, 11, 13, 15], "uniform": 6, "resourc": [6, 12], "identifi": 6, "liga\u00e7\u00f5": [6, 9], "supor": 6, "situa\u00e7\u00e3": 6, "dummy": [6, 8], "db_codec": 6, "utf": 6, "check_reserved": 6, "migrate_enabled": 6, "fake_migrate_all": 6, "decode_credentials": 6, "driver_args": 6, "adapter_args": 6, "attempts": [6, 12], "auto_import": 6, "bigint_id": 6, "lazy_tabl": 6, "db_uid": 6, "do_connect": 6, "after_connection": 6, "ignore_field_cas": 6, "entity_quoting": 6, "table_hash": 6, "estabelec": 6, "atrav\u00e9s": [6, 8, 9, 12], "inst\u00e2nc": [6, 13], "dar": 6, "seq\u00fc\u00eanc": 6, "set_encoding": 6, "utf8mb4": 6, "postgr": 6, "2005": 6, "mssql3": 6, "2012": 6, "mssql4": 6, "oracl": 6, "dsn": 6, "uid": 6, "pwd": 6, "ndb": 6, "consists": [6, 13], "locked": 6, "accessed": [6, 9, 11], "established": 6, "appropriately": 6, "encoding": 6, "avoids": 6, "utf8": 6, "charact": [6, 11], "unicod": [6, 11], "characters": [6, 11], "consist": [6, 14], "four": 6, "bytes": 6, "turns": [6, 15], "buff": 6, "often": [6, 12, 15], "completely": [6, 8, 12], "had": 6, "connecting": 6, "_select": 6, "_insert": 6, "_updat": 6, "_delet": 6, "usos": 6, "codific": 6, "caract": [6, 9], "latin1": 6, "unicodedecodeerror": 6, "rath": [6, 8, 11, 15], "slow": 6, "establish": 6, "pooling": 6, "closed": [6, 8], "goes": [6, 11], "tri": [6, 14], "recycl": 6, "cresc": 6, "m\u00e1xim": 6, "simult\u00e2n": 6, "receb": [6, 8], "tamanh": 6, "sequencial": 6, "t\u00f3pic": 6, "benef\u00edci": 6, "conseg": 6, "segund": 6, "fracass": 6, "permanec": 6, "fech": [6, 8, 9], "grac": 6, "repeti\u00e7\u00e3": 6, "restabelec": 6, "interromp": 6, "major": 6, "boost": [6, 8], "creation": 6, "deferred": [6, 15], "referenced": [6, 7], "possibly": 6, "concurrency": [6, 15], "problems": [6, 12, 15], "howev": [6, 8, 11, 15], "demand": 6, "referred": [6, 7], "responsibility": 6, "housekeeping": 6, "definitions": [6, 7, 14], "maintainability": 6, "expect": [6, 11], "uris": 6, "deal": [6, 15], "distribut": 6, "workload": 6, "primeir": [6, 8, 12, 16], "eo": 6, "terceir": 6, "distribu": 6, "carg": 6, "mestr": 6, "escrav": 6, "colun": [6, 13], "alvo": 6, "nenhum": [6, 12], "cont\u00eam": [6, 10], "against": [6, 7, 9, 11, 13], "ordem": 6, "op\u00e7\u00f5": [6, 16], "extras": 6, "tais": 6, "ends": [6, 8, 14], "reserved": [6, 11], "keywords": 6, "append": [6, 9, 11, 13, 15], "_nonreserved": 6, "postgres_nonreserved": 6, "backends": 6, "cit": [6, 9, 11], "entidad": 6, "identific": [6, 9], "ger": [6, 8, 9, 12, 13], "n\u00edvel": [6, 10], "cot": 6, "mai\u00fascul": 6, "min\u00fascul": 6, "assim": [6, 8], "dobr": 6, "motor": 6, "acord": 6, "norm": 6, "conform": [6, 12], "dobrag": 6, "certez": 6, "esquem": 6, "organiz": 6, "ambas": 6, "acim": [6, 7, 8, 12], "table1": 6, "column": [6, 13], "\u00c0s": [6, 9], "recomend": 6, "detalh": [6, 13], "user_nam": 6, "user_password": 6, "server_addr": 6, "db_nam": 6, "sslmod": 6, "sslrootcert": 6, "root": [6, 8, 12, 13], "sslcert": 6, "sslkey": 6, "migration": 6, "migrations": 6, "booleans": 6, "afet": 6, "migr": 6, "desat": 6, "verdadeir": [6, 11, 13], "aren": 6, "committed": [6, 14], "immediately": 6, "depending": [6, 8, 11, 13], "activiti": 6, "ever": 6, "granul": 6, "bob": 6, "roll": 6, "revert": 6, "views": 6, "enclosed": 6, "pseud": 6, "traceback": 6, "ticket": 6, "visitor": 6, "tablenam": [6, 7, 11, 15], "kwargs": [6, 15], "preenchiment": 6, "obrigat\u00f3ri": 6, "subcl": 6, "defini\u00e7\u00e3": 6, "opcion": [6, 9], "common_filt": 6, "discut": 6, "abaix": 6, "loj": 6, "pesso": 6, "peg": 6, "increment": 6, "original": [6, 8, 9, 11], "external": 6, "elements": [6, 9, 11, 15], "redefini\u00e7\u00e3": 6, "provoc": 6, "anonymous": [6, 8, 11], "downs": 6, "othertabl": 6, "otherfield": 6, "referencing": 6, "constructor": [6, 12, 16], "representation": [6, 9], "backend": 6, "ali": [6, 11], "constructing": 6, "illustrat": 6, "qualified": 6, "belonging": 6, "db1": 6, "dbo": 6, "helps": [6, 8, 11], "legacy": 6, "primary": [6, 8, 12], "keys": [6, 7], "keyed": 6, "sub": 6, "refer": [6, 7], "relev": 6, "num\u00e9r": 6, "acion": 6, "embor": 6, "mudanc": 6, "din\u00e2m": [6, 16], "mes": [6, 9], "vantagens": 6, "adi": 6, "age": 6, "set_attribut": 6, "is_not_empty": 6, "is_int_in_rang": 6, "120": 6, "realment": 6, "defini\u00e7\u00f5": 6, "ea": 6, "entant": [6, 8, 9, 13], "tom": 6, "is_in_db": [6, 13], "sometabl": 6, "somefield": 6, "some_valu": 6, "caus": [6, 11], "in\u00edci": 6, "keyword": [6, 8, 9], "attached": [6, 12], "preceded": [6, 11], "underscor": [6, 9, 11], "naming": 6, "conflicts": 6, "_extr": 6, "condi\u00e7\u00f5": 6, "easiest": 6, "conditions": [6, 11], "met": [6, 8, 9], "uniqu": [6, 7, 11], "autom\u00e1t": 6, "account": 6, "accnum": 6, "acctype": 6, "accdesc": 6, "null": [6, 7, 11, 15], "fieldnam": [6, 15], "dispon": 6, "escrit": [6, 8], "garant": [6, 8], "obras": 6, "simplific": 6, "vis\u00e3": 6, "Estes": 6, "length": [6, 11], "ondelet": 6, "notnull": 6, "uploadfield": 6, "widget": [6, 11], "label": [6, 7, 8, 11, 15], "comment": [6, 9], "searchabl": 6, "listabl": 6, "authoriz": [6, 7], "autodelet": 6, "uploadfold": 6, "uploadseparat": 6, "uploadfs": 6, "custom_qualifi": 6, "map_non": 6, "Nem": 6, "compriment": 6, "compat": 6, "constru\u00edd": [6, 13], "preench": 6, "previ": [6, 11], "apropri": 6, "ness": 6, "validator": [6, 11], "foruml\u00e1ri": [6, 9, 16], "enforced": [6, 11, 14], "forms": [6, 9, 13, 14, 15], "addition": [6, 8], "sometim": [6, 8, 9, 11], "seem": 6, "redundant": 6, "maintain": [6, 8, 15], "distinction": 6, "efet": 6, "instru\u00e7\u00e3": 6, "cascat": 6, "exclu": 6, "desativ": 6, "conjunt": [6, 9, 10, 13], "imped": [6, 9], "nul": 6, "exclus": 6, "appli": [6, 11, 15], "somewher": 6, "uploads": 6, "discussed": [6, 8, 9, 11], "detail": [6, 9, 13, 15], "uploaded": [6, 11, 14], "upload_fold": [6, 11], "points": [6, 7, 11, 13], "optimized": 6, "subfold": 6, "attention": 6, "breaking": 6, "eith": [6, 11], "separat": [6, 11, 12, 15], "changing": 6, "behavior": [6, 11, 14, 15], "prevent": [6, 9, 11], "happens": 6, "mov": 6, "amazon": 6, "s3": 6, "sftp": 6, "pyfilesyst": 6, "funcion": [6, 16], "deleted": 6, "due": [6, 7, 11], "operation": [6, 12], "trigg": [6, 15], "associated": [6, 11, 12], "autogenerated": 6, "grav\u00e1vel": 6, "formul\u00e1ri": [6, 12, 13, 16], "leg\u00edvel": 6, "autentic": 6, "autoriz": 6, "guaranteed": [6, 14], "reset": 6, "is_length": 6, "512": 6, "32": [6, 11], "768": 6, "31": [6, 7, 11], "gib": 6, "boolean": [6, 13], "is_float_in_rang": 6, "1e100": 6, "decimal": [6, 11], "is_decimal_in_rang": 6, "is_dat": 6, "is_tim": 6, "is_datetim": 6, "_id": [6, 9, 11, 15], "is_empty_or": 6, "is_json": 6, "bigint": 6, "63": [6, 11], "grand": 6, "devolv": [6, 8], "pont": 6, "respect": [6, 11, 13], "certain": [6, 8, 11, 13], "denormalization": 6, "listproperty": 6, "stringlistproperty": 6, "relational": 6, "lists": [6, 13], "items": [6, 7, 8, 11], "item": [6, 8, 9], "escaped": [6, 8, 9], "pretty": [6, 11, 14], "explanatory": 6, "backported": 6, "portability": 6, "base64": [6, 8, 15], "decoded": 6, "extracted": 6, "negativ": [6, 11], "33": [6, 11], "spac": [6, 8, 11, 13], "necessary": [6, 8, 9], "making": [6, 8], "communication": 6, "escaping": [6, 8], "maior": [6, 7], "_format": 6, "sublinh": 6, "poss\u00edv": 6, "Da": 6, "pai": 6, "_tabl": [6, 11, 15], "_tablenam": 6, "_db": 6, "constru\u00e7\u00e3": [6, 9, 13], "v\u00ea": 6, "validat": [6, 11], "tupl": [6, 8, 9], "consider": [6, 8, 9], "myfil": 6, "imag": [6, 8, 9, 11], "relat": 6, "atribu\u00edd": 6, "habilit": 6, "duplicat": 6, "file_content": [6, 15], "file_nam": [6, 15], "feit": 6, "ocasional": 6, "mei": 6, "rb": 6, "Tamb\u00e9m": 6, "ficheir": 6, "corrent": 6, "lev": [6, 9], "flux": 6, "extens\u00e3": 6, "tempor\u00e1ri": 6, "assoc": [6, 10], "inv\u00e9s": 6, "image_fil": 6, "opost": 6, "fullnam": 6, "nameonly": 6, "recup": 6, "pront": 6, "clos": [6, 9], "contextlib": 6, "shutil": 6, "wb": 6, "copyfileobj": 6, "definition": [6, 7, 11, 13], "differs": [6, 13], "letting": 6, "removing": 6, "second": [6, 9, 11, 12, 14], "adding": [6, 11, 13], "newly": 6, "referim": 6, "logfil": 6, "unnamed": 6, "Nos": 6, "descart": 6, "columns": [6, 16], "recommended": [6, 11], "disabl": [6, 13], "impor": 6, "solt": 6, "hor": 6, "lix": 6, "fins": 6, "reclam": 6, "an\u00e1lis": 6, "corromp": 6, "quest\u00e3": 6, "cons": 6, "actualiz": 6, "gen\u00e9r": 6, "t\u00edpic": 6, "transa\u00e7\u00f5": 6, "menor": 6, "\u00e9poc": 6, "comet": 6, "fic": 6, "envolv": 6, "convers\u00e3": 6, "acontec": 6, "exat": [6, 8], "reconstru": 6, "metad": 6, "aquel": [6, 12], "attempting": 6, "prudent": 6, "yourapp": 6, "descrev": 6, "estreit": 6, "fak": 6, "alex": 6, "trunc": 6, "reinic": 6, "recomec": 6, "identity": [6, 7, 11, 14], "bulk_insert": 6, "inser\u00e7\u00f5": 6, "relacion": 6, "vantag": 6, "looping": 6, "veloc": 6, "caiu": 6, "tr\u00eas": 6, "carl": 6, "pertenc": 6, "q": [6, 13], "tal": 6, "escrev": [6, 8, 9], "fat": 6, "birthplac": 6, "chicag": 6, "houv": 6, "nasc": 6, "terr": 6, "natal": 6, "crit\u00e9ri": 6, "selec\u00e7\u00e3": 6, "pet": [6, 7, 13], "rov": 6, "ret": 6, "fianc": 6, "erros": 6, "mant\u00e9m": 6, "mapeament": 6, "cuj": 6, "rar": 6, "similar": [6, 9], "etiquet": [6, 9], "propriedad": 6, "anex": 6, "tools": [6, 12, 15], "properti": [6, 11], "id1": 6, "cha": 6, "id2": 6, "material": 6, "wood": 6, "assert": 6, "implemented": 6, "thing_tags_default": 6, "tail": 6, "flexibl": [6, 12], "instru\u00e7\u00f5": 6, "u": [6, 9], "seleccion": 6, "cinc": 6, "placeholders": 6, "colnam": 6, "as_ordered_dict": 6, "substitu\u00edd": 6, "espac": [6, 10, 13], "results": [6, 8, 11, 13], "cursor": [6, 15], "dictionari": 6, "applying": [6, 13], "field1": 6, "val1_row1": 6, "field2": 6, "val2_row1": 6, "val1_row2": 6, "val2_row2": 6, "bonit": 6, "tecl": [6, 10], "ordereddict": 6, "reflet": 6, "adi\u00e7\u00e3": 6, "extra\u00edd": 6, "express\u00e3": [6, 7, 10, 13], "quaisqu": [6, 11], "r\u00f3tul": [6, 9, 13], "arbitr\u00e1ri": 6, "fict\u00edci": 6, "rea": 6, "\u00fatil": 6, "depur": [6, 8], "selecting": 6, "timed": 6, "_timings": 6, "took": 6, "expl\u00edcit": [6, 12], "recorrent": 6, "myidx": 6, "dialet": 6, "SE": 6, "direct": 6, "sid": [6, 15, 16], "comand": 6, "_count": 6, "susan": 6, "iterabl": [6, 11], "whos": [6, 8, 11], "act": 6, "diff": 6, "latt": [6, 11, 14], "fileir": 6, "loop": [6, 8], "imprim": 6, "etap": 6, "entend": 6, "pergunt": 6, "particularly": 6, "handy": 6, "compact": 6, "nota\u00e7\u00e3": [6, 9], "sim": 6, "incomum": 6, "delete_record": 6, "iterators": 6, "avali": [6, 8], "aliment": 6, "lac": 6, "tradicion": 6, "tradicional": 6, "dramat": 6, "iterselect": 6, "cerc": 6, "m\u00e1quin": [6, 9], "reescrev": 6, "sele\u00e7\u00e3": [6, 9], "tir": 6, "proveit": 6, "repr_row": 6, "gerador": 6, "wouldn": 6, "anyway": 6, "myrecord": 6, "shortcut": [6, 8, 11], "19": [6, 7, 11], "02": [6, 11], "convenient": [6, 16], "aparent": 6, "flex\u00edvel": 6, "verif": 6, "levant": 6, "cois": 6, "owner_id": 6, "vincul": 6, "ineficient": 6, "tr\u00e1s": 6, "owns": 6, "actu": 6, "decomp\u00f5": 6, "s\u00e9ri": 6, "classific": [6, 13], "invers": 6, "til": 6, "aparec": 6, "aleat\u00f3r": 6, "random": [6, 8, 11, 12, 15], "super": 6, "concaten": 6, "grup": [6, 12], "condicional": 6, "condi\u00e7\u00e3": 6, "query1": 6, "query2": 6, "consulta1": 6, "efeit": 6, "necessit": 6, "sen\u00e3": 6, "desloc": 6, "offset": [6, 7], "m\u00e1x": 6, "implicit": 6, "pagin": [6, 13], "desempenh": 6, "involved": [6, 8], "managing": 6, "sections": 6, "respectively": [6, 11], "cacheabl": 6, "caching": [6, 16], "trad": 6, "offs": 6, "bin\u00e1ri": 6, "neg": 6, "invert": [6, 11], "nega\u00e7\u00e3": 6, "un\u00e1ri": 6, "sobrecarg": 6, "forma\u00e7\u00e3": 6, "precedent": 6, "compar": [6, 14], "par\u00eantes": 6, "unit\u00e1ri": 6, "elev": 6, "negated": 6, "constru": [6, 9, 13], "vaz": 6, "contag": 6, "jog": [6, 10], "elimin": [6, 13], "ken": 6, "visits": 6, "clicks": [6, 15], "cl\u00e1usul": [6, 8], "condition": [6, 11, 16], "yes_or_n": 6, "curt": 6, "confund": 6, "philip": 6, "modified_on": 6, "retain": 6, "mindful": 6, "inclu\u00edd": [6, 9], "compost": [6, 9], "vari": 6, "usam": 6, "first_row": 6, "last_row": 6, "obvi": 6, "indo": 6, "pretend": 6, "esquec": 6, "table_nam": 6, "consegu": 6, "otimiz": 6, "rows_list": 6, "first_row_dict": 6, "themselv": [6, 8], "allowed": [6, 7, 11, 12], "rows1": 6, "rows2": 6, "uni\u00e3": 6, "rows3": 6, "remo\u00e7\u00e3": 6, "intersec\u00e7\u00e3": 6, "in\u00fatil": 6, "manipul": 6, "inalter": 6, "origin": 6, "signific": [6, 8], "\u00f3bvi": [6, 8], "element": [6, 8, 9, 11, 13, 15], "validad": 6, "cache_db_select": 6, "cache\u00e1vel": 6, "falt": 6, "aceler": 6, "calcul": 6, "unit_pric": 6, "quantity": [6, 11], "total_pric": 6, "rid": 6, "99": 6, "9": [6, 7, 11, 13, 14], "95": 6, "1l": 6, "retrieval": 6, "wik": [6, 7], "searching": [6, 9], "normalized": 6, "evaluated": [6, 7], "subse\u00e7\u00e3": 6, "dif": 6, "daquel": 6, "se\u00e7\u00e3": 6, "discounted_total": 6, "discount": 6, "impl\u00edcit": 6, "pens": 6, "niss": 6, "percentual": 6, "dig": 6, "15": [6, 11, 12, 13], "appe": [6, 11], "cont\u00eain": 6, "instanci": 6, "myvirtualfields": 6, "virtualfields": 6, "order_it": 6, "agir": [6, 9], "setvirtualfields": 6, "myvirtualfields1": 6, "discounted_unit_pric": 6, "90": [6, 7], "myvirtualfields2": 6, "discounted_total_pric": 6, "lazy_total_pric": 6, "ilustr": [6, 8], "owner": [6, 11], "intended": [6, 15], "ex": 6, "c\u00edclic": 6, "insir": 6, "boat": 6, "sho": 6, "adquir": 6, "jun\u00e7\u00e3": [6, 12], "transparent": [6, 8, 11], "si": [6, 13], "unid": 6, "owner_id1": 6, "owner_id2": 6, "with_al": 6, "particip": 6, "esquerd": [6, 13], "jun\u00e7\u00f5": 6, "pertencent": 6, "propriet\u00e1ri": 6, "incorpor": 6, "clar": 6, "igual": [6, 7], "pr\u00f3pri": [6, 13], "ajust": 6, "barc": 6, "intermedi\u00e1r": 6, "ownership": 6, "reescrit": 6, "co": [6, 11], "propriet\u00e1r": 6, "vias": 6, "persons_and_things": 6, "rela\u00e7\u00f5": 6, "father_id": 6, "mother_id": 6, "deriv": 6, "fid": 6, "mid": 6, "claud": 6, "fath": 6, "moth": 6, "AS": 6, "opt\u00e1m": 6, "distin\u00e7\u00e3": 6, "comunic": 6, "diferenc": 6, "sutil": 6, "errad": 6, "corret": 6, "event": 6, "event_tim": 6, "gravidad": 6, "severity": 6, "Como": [6, 16], "varredur": 6, "inje\u00e7\u00e3": 6, "xss": [6, 8, 9], "scan": 6, "injection": 6, "unauthorized": 6, "personag": 6, "sinal": 6, "wild": 6, "card": 6, "sequ\u00eanc": 6, "ansi": 6, "COMO": 6, "sens\u00edvel": 6, "case_sensitiv": 6, "aproxim": 6, "meaning": [6, 11, 12], "value1": 6, "value2": 6, "grau": 6, "apoi": 6, "gost": 6, "2018": 6, "aninh": [6, 9], "bad_days": 6, "jonathan": 6, "nested_select": 6, "m\u00e9d": 6, "estam": 6, "43": 6, "substring": [6, 11], "pux": 6, "sysus": 6, "pow": [6, 15], "coa": 6, "matem\u00e1t": 6, "resgat": 6, "dumpfil": 6, "converting": 6, "produc": [6, 9, 11, 12], "newlin": [6, 11], "export_to_csv_fil": 6, "explict": 6, "exporting": 6, "importing": 6, "finds": 6, "assigned": [6, 11, 12, 14], "ids": 6, "restaur": 6, "somefil": 6, "field3": 6, "separ": [6, 13], "extrem": 6, "despej": 6, "suficient": [6, 13], "ter\u00e3": 6, "nid": 6, "c\u00f3p": 6, "mescl": 6, "acompanh": 6, "64": [6, 8, 15], "stringi": 6, "set_head": 6, "getvalu": 6, "import_and_sync": 6, "_type": [6, 9, 11, 15], "_nam": [6, 9, 11, 15], "vars": [6, 9, 11, 14], "rpc": 6, "nel": 6, "rotul": 6, "uuids": 6, "preocup": 6, "thead": 6, "tr": 6, "th": 6, "tbody": 6, "w2p_odd": 6, "odd": [6, 8], "td": 6, "w2p_even": 6, "easily": [6, 8, 9, 11, 13], "f": [6, 12, 13, 14, 15], "brut": 6, "quotech": 6, "aspas": 6, "cota\u00e7\u00e3": 6, "quote_minimal": 6, "oufil": 6, "quote_nonnumeric": 6, "35": 6, "description": [6, 7, 11, 12], "2013": 6, "oficial": 6, "itens": 6, "escap": [6, 8, 9], "is_in_set": 6, "toy": 6, "car": 6, "products": 6, "costum": 6, "requisit": [6, 16], "particular": 6, "normaliz": 6, "restri\u00e7\u00e3": 6, "got": 6, "v\u00edrgul": 6, "leitur": 6, "gend": 6, "doctor": 6, "specialization": 6, "fict\u00edc": 6, "is_activ": 6, "created_on": 6, "created_by": 6, "modified_by": 6, "payment": 6, "amount": 6, "princ\u00edpi": [6, 16], "activ": [6, 12], "herd": 6, "certifiqu": [6, 10], "accomplished": [6, 12, 14], "anyobj": 6, "obj": [6, 8], "dumps": [6, 15], "loads": [6, 15], "myobj": 6, "aid": 6, "myobjnam": 6, "accomplish": 6, "sqlcustomtyp": 6, "seis": 6, "_before_insert": 6, "_after_insert": 6, "_before_updat": 6, "_after_updat": 6, "_before_delet": 6, "_after_delet": 6, "acrescent": 6, "ressalv": 6, "pprint": 6, "callback": [6, 12, 15], "before_insert": 6, "after_insert": 6, "oprow": 6, "before_updat": 6, "after_updat": 6, "before_delet": 6, "after_delet": 6, "especializ": 6, "_antes_": 6, "dispar": 6, "infinit": 6, "update_naiv": 6, "schem": [6, 11], "relationships": 6, "deletions": 6, "known": [6, 8], "cascading": 6, "informed": 6, "consequenc": [6, 11, 15], "deletion": 6, "enable_record_versioning": 6, "stored_it": 6, "ocult": [6, 11], "padroniz": 6, "vers\u00f5": 6, "_enable_record_versioning": 6, "archive_db": 6, "archive_nam": 6, "stored_item_archiv": 6, "current_record": 6, "grav": 6, "escond": 6, "generaliz": 6, "tenancy": 6, "blog_post": 6, "subject": [6, 7, 12], "post_text": 6, "is_public": 6, "posts": 6, "p\u00fablic": 6, "_common_filt": 6, "fras": 6, "visualiz": 6, "ignore_common_filters": [6, 11], "common_filters": 6, "suponh": 6, "enderec": 6, "ip": [6, 11], "ip2int": 6, "sv": 6, "ipv4": [6, 11], "sp": 6, "split": [6, 7, 8, 11], "iip": 6, "int2ip": 6, "iv": 6, "ov": 6, "divmod": 6, "256": 6, "nativ": 6, "encod": 6, "decod": 6, "websit": [6, 15], "ipaddr": 6, "wikiped": [6, 7], "91": 6, "198": 6, "174": 6, "192": [6, 11], "172": [6, 11], "217": 6, "11": [6, 11], "74": 6, "125": 6, "65": 6, "207": 6, "97": 6, "227": 6, "239": 6, "f\u00e1bric": 6, "Seu": 6, "quant": 6, "marked": 6, "across": [6, 8], "v\u00e3": 6, "fug": [6, 8], "fas": 6, "db_a": 6, "db_b": 6, "simultan": 6, "distributed_transaction_commit": 6, "desfaz": 6, "possibil": 6, "suced": 6, "estiv": 6, "mydb": 6, "font": [6, 8, 11, 13], "cam": [6, 10], "abstra\u00e7\u00e3": 6, "2010": 6, "connectionpool": 6, "baseadapt": 6, "extends": [6, 8, 15], "se\u00e7\u00f5": 6, "conf": 6, "myvalu": 6, "deleg": 6, "_adapt": 6, "_listify": 6, "list_of_fields": 6, "constr\u00f3": 6, "book": 6, "sqliteadapt": 6, "jdbcsqliteadapt": 6, "mysqladapt": 6, "postgresqladapt": 6, "jdbcpostgresqladapt": 6, "oracleadapt": 6, "mssqladapt": 6, "mssql2adapt": 6, "mssql3adapt": 6, "mssql4adapt": 6, "firebirdadapt": 6, "firebirdembeddedadapt": 6, "informixadapt": 6, "db2adapt": 6, "ingresadapt": 6, "ingresunicodeadapt": 6, "googlesqladapt": 6, "nosqladapt": 6, "googledatastoreadapt": 6, "cubridadapt": 6, "teradataadapt": 6, "sapdbadapt": 6, "couchdbadapt": 6, "imapadapt": 6, "mongodbadapt": 6, "verticaadapt": 6, "sybaseadapt": 6, "char": 6, "varch": 6, "longtext": 6, "credential_decod": 6, "pool_connection": 6, "foreign_key_checks": 6, "sql_mod": 6, "no_backslash_escap": 6, "lastrowid": 6, "last_insert_id": 6, "fetchon": 6, "olhand": 6, "mapping": [6, 11, 14], "couchdb": 6, "ibm_db_dbi": 6, "db2ibm": 6, "db2pyodbc": 6, "firebird_embedded": 6, "firebirdembedded": 6, "googlemysql": 6, "googledatastor": 6, "googlepostgr": 6, "googlesql": 6, "informixs": 6, "ingresu": 6, "ingresunicod": 6, "jdbc": 6, "jdbcpostgr": 6, "jdbcsqlit": 6, "mong": 6, "mssql1": 6, "mssql2": 6, "mssql1n": 6, "mssql3n": 6, "mssql4n": 6, "mssqln": 6, "postgres2": 6, "postgrenew": 6, "postgrepsyconew": 6, "postgres3": 6, "postgreboolean": 6, "postgrepsycoboolean": 6, "postgrepsyc": 6, "pytds": 6, "sap": 6, "spatialit": 6, "vertic": 6, "parsed": 6, "obtained": 6, "sqladapt": 6, "recognized": 6, "mysqldv": 6, "dropping": 6, "altering": 6, "remain": [6, 11], "invisibl": 6, "decid": [6, 11, 13], "reinstat": 6, "fail": [6, 8, 11], "rebuilt": 6, "awar": 6, "extract": [6, 11], "cient": 6, "failur": [6, 11, 15], "unfortunat": 6, "prevented": 6, "fixed": [6, 14], "aftermath": 6, "migrated": 6, "corruption": 6, "migrating": [6, 14], "deleting": [6, 13], "corrupted": 6, "py4web_filesyst": 6, "fetch": [6, 14, 15], "reforc": 6, "raz\u00f5": 6, "superseeded": 6, "obsolet": 6, "circul": 6, "bug": 6, "on_delete_action": 6, "NO": 6, "alcanc": 6, "agreg": 6, "run_in_transaction": 6, "seq\u00fcenc": 6, "liststringproperty": 6, "restful": [7, 12], "cit0801": 7, "inspired": 7, "graphql": 7, "cit0802": 7, "less": [7, 11, 14], "powerful": [7, 12, 14], "spirit": 7, "easi": [7, 8], "denormaliz": 7, "policy": 7, "impli": 7, "disabled": [7, 9, 11], "fields": [7, 11, 12, 13, 14, 16], "specifications": 7, "might": [7, 11], "superhero": 7, "isdir": [7, 11, 13], "job": [7, 11, 13], "strength": 7, "entri": [7, 11, 13], "clark": [7, 13], "kent": [7, 13], "journalist": [7, 13], "park": [7, 13], "photograph": [7, 13], "bruc": [7, 13], "wayn": [7, 13], "ceo": [7, 13], "spiderman": [7, 13], "batman": [7, 11, 13], "flight": 7, "speed": [7, 8], "durability": 7, "75": 7, "80": [7, 11], "70": 7, "allowed_patterns": 7, "reasons": 7, "rec_id": 7, "allow": [7, 8, 9, 11, 12, 13, 15], "deny": 7, "record_id": [7, 15], "get_vars": [7, 14], "post_vars": [7, 14], "patterns": 7, "pattern": 7, "expost": 7, "let": [7, 11, 15], "diagr": 7, "interpreting": 7, "posted": 7, "formdat": 7, "eq": 7, "gt": [7, 9], "queried": 7, "sup": 7, "her\u00f3": 7, "ident": 7, "refers": 7, "superpotent": 7, "preceding": 7, "linked": 7, "v\u00f4o": 7, "said": 7, "record": [7, 11, 12, 14, 15], "equal": [7, 11, 12, 15], "modifiers": 7, "obvious": [7, 8, 12], "status": 7, "200": [7, 11, 14, 15], "2019": 7, "05": 7, "19t05": 7, "38": 7, "00": 7, "132635": 7, "api_version": 7, "2021": 7, "01": 7, "04t07": 7, "466030": 7, "regex": [7, 9, 11], "post_writabl": 7, "referenced_by": 7, "put_writabl": 7, "178974": 7, "desnormaliz": 7, "renom": 7, "123218": 7, "recolh": [7, 13], "559918": 7, "201988": 7, "322494": 7, "readability": 7, "powers": 7, "309903": 7, "355181": 7, "34": 7, "974953": 7, "405515": 7, "366288": 7, "451907": 7, "453020": 7, "iso": 7, "8601": 7, "matching": [7, 9, 11], "representational_state_transf": 7, "distinct": [8, 11], "rendering": [8, 9], "reno": 8, "plus": [8, 12, 13], "minor": [8, 14], "trickery": 8, "seamlessly": 8, "squar": 8, "brackets": [8, 11], "embedded": [8, 11], "angle": 8, "editors": 8, "mix": 8, "soon": [8, 15], "bracket": 8, "separating": [8, 11], "embedding": 8, "indented": 8, "according": 8, "rul": [8, 11, 13], "un": [8, 9, 10, 15], "indentation": 8, "blocks": 8, "ending": 8, "colon": 8, "beginning": [8, 11, 12], "palavr": [8, 9, 10], "emacs": 8, "divis\u00e3": 8, "bloc": [8, 9], "indent": 8, "br": [8, 9], "report": 8, "debugged": 8, "dom": [8, 16], "inspector": 8, "introduz": 8, "mediant": 8, "corp": [8, 9], "interior": 8, "p\u00e1gin": [8, 9, 13, 16], "vulner": 8, "transparently": 8, "replaced": [8, 9, 11, 15], "dummyrespons": 8, "practic": [8, 11], "editing": [8, 9, 11, 12, 13], "new_app": [8, 9], "iter": 8, "ul": [8, 11, 12, 15], "li": [8, 11, 12, 15], "apresent": 8, "condicion": 8, "randint": [8, 12], "h2": [8, 11], "45": [8, 11], "encerr": 8, "incorret": 8, "divisibl": [8, 11], "statements": [8, 11], "division": [8, 9], "excep\u00e7\u00e3": 8, "ol\u00e1": 8, "itemize1": 8, "href": [8, 9, 12, 13], "itemize2": 8, "exact": [8, 14], "represent": [8, 9, 11], "pedac": 8, "frent": 8, "terminat": [8, 15], "statement": 8, "modifying": 8, "tre": [8, 11], "foot": 8, "convention": [8, 11, 14], "minimalist": [8, 14], "minimalist_pag": 8, "extended": 8, "titl": [8, 11, 15], "recursively": 8, "resulting": [8, 11, 15], "bytecod": 8, "pyc": 8, "entir": [8, 14, 15], "inserted": 8, "sidebar_enabled": 8, "hom": 8, "excerpt": 8, "sideb": 8, "assignment": 8, "gets": [8, 12], "anywher": [8, 14], "worth": 8, "though": 8, "impos": 8, "limitation": 8, "noted": 8, "determined": [8, 11], "compiling": 8, "significant": 8, "avoided": 8, "alternativ": [8, 11], "some_condition": 8, "this_templat": 8, "that_templat": 8, "compilation": 8, "layouts": [8, 13], "encapsulat": [8, 9], "commonality": 8, "footers": 8, "menus": [8, 11], "mysideb": 8, "contents": [8, 9, 15], "predefin": 8, "enclosing": 8, "over": [8, 13, 15], "riding": 8, "consistency": 8, "desir": 8, "substitutions": 8, "ships": 8, "doctyp": [8, 13], "viewport": 8, "width": [8, 11], "devic": 8, "initial": [8, 11], "rel": [8, 13], "icon": [8, 13], "aaabaaeaaqeaaaeaiaawaaaafgaaacgaaaabaaaaagaaaaeaiaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaapaaaaa": 8, "stylesheet": [8, 13], "cdnjs": [8, 13], "cloudflar": [8, 13], "ajax": [8, 13, 15], "libs": [8, 9, 13], "awesom": [8, 13], "14": [8, 11, 13], "min": [8, 11, 13], "integrity": 8, "sha512": [8, 11], "1pkogiy59xj8co8": 8, "ne6fz": 8, "loazkjy": 8, "ky8iq0g4b3cyey6wyhn3yt9pw0xpsrivlkmxe40ptknxrlnz9": 8, "fkdaog": 8, "crossorigin": 8, "margin": [8, 15], "top": [8, 11, 12], "16px": 8, "8em": [8, 15], "page_head": 8, "customiz": [8, 11, 13], "navigation": [8, 15], "bar": [8, 13], "nav": 8, "black": [8, 11], "Do": [8, 11, 13, 16], "touch": 8, "hamburg": 8, "checkbox": [8, 9], "left": [8, 11, 13, 15], "menu": [8, 11, 12], "page_left_menu": 8, "navb": [8, 12], "profil": [8, 12, 14], "change_password": [8, 12], "flash": [8, 15, 16], "padded": [8, 14], "contect": 8, "mad": [8, 11, 13], "gott": 8, "page_scripts": 8, "scripts": [8, 9], "few": [8, 12, 15], "html5": 8, "58": 8, "accordingly": 8, "54": 8, "123": [9, 11], "myclass": 9, "thisisatest": 9, "programmatically": [9, 11], "positional": 9, "interpreted": 9, "contained": [9, 11, 13], "named": 9, "dicion\u00e1ri": [9, 10, 11], "metatag": 9, "tagg": 9, "xmlescap": 9, "expressions": [9, 11, 15], "prest": 9, "lt": 9, "strings": [9, 11, 13], "equivalently": 9, "shell": 9, "concatenating": 9, "rela\u00e7\u00e3": 9, "ab": [9, 11], "yb": 9, "h\u00edfens": 9, "_dat": 9, "collapsibl": 9, "hyphen": 9, "notation": 9, "pairs": 9, "dinam": 9, "soap": 9, "whatev": [9, 15], "_xmlns": 9, "xmlns": 9, "renderiz": 9, "insegur": 9, "entered": 9, "scripting": 9, "visitors": [9, 11], "injections": 9, "permitted_tags": 9, "allowed_attribut": 9, "blockquot": 9, "target": [9, 15], "alt": 9, "colspan": 9, "assistent": 9, "_bgcolor": 9, "bgcolor": 9, "concatenat": 9, "bold": 9, "insist": 9, "_action": 9, "_method": 9, "headings": [9, 13], "subheadings": 9, "cabec": [9, 13], "tagging": [9, 12], "it\u00e1l": 9, "embed": 9, "_src": 9, "png": [9, 11], "_alt": 9, "inclus\u00e3": 9, "est\u00e1t": [9, 16], "envi": [9, 13], "r\u00e1di": 9, "_valu": [9, 11, 15], "radi": [9, 11], "buttons": [9, 11, 13, 15], "_checked": 9, "checked": [9, 11], "stands": 9, "ordered": 9, "_selected": 9, "thank": 9, "ok": [9, 14], "pr\u00e9": [9, 13, 16], "prefer": 9, "listagens": 9, "marca\u00e7\u00e3": 9, "whit": [9, 15], "Estas": 9, "junt": [9, 13], "matriz": 9, "posicion": 9, "rows": [9, 11, 13, 14], "oposi\u00e7\u00e3": 9, "rodap": 9, "_cols": 9, "_rows": 9, "cols": 9, "monoespac": 9, "unordered": 9, "generator": 9, "_c": 9, "suprim": 9, "aut": 9, "fechament": 9, "termin": 9, "agrad": 9, "retribu": 9, "0x7fa533ff7640": 9, "kargs": 9, "supplied": 9, "first_only": 9, "z": [9, 11], "jquery": [9, 11, 15], "accepting": 9, "selector": [9, 15], "selector1": 9, "selector2": 9, "selectorn": 9, "descendant": 9, "ancestor": 9, "equals": [9, 12], "unquoted": 9, "_u": 9, "_disabled": 9, "specifying": 9, "abc": [9, 11], "xyz": [9, 11], "callabl": [9, 11], "el": 9, "searched": [9, 13], "textual": 9, "efg": 9, "factori": 9, "injecting": 9, "timeoffset": 9, "sidebar_menu": 9, "i18n": 10, "p10n": 10, "fr": 10, "dog": [10, 11, 15], "tantissim": 10, "superior": 10, "express\u00f5": 10, "plural": 10, "chec": 10, "bed": 10, "postel": 10, "cachorr": 10, "deform": 10, "reserv": 10, "max": [10, 11, 15], "idiom": 10, "envolt": 10, "find_match": 10, "update_languag": 10, "supported": [10, 11, 12], "german": 10, "known_expressions": 10, "high": [11, 12], "deletabl": [11, 13], "formstyl": [11, 13, 15], "formstyledefault": [11, 13], "dbi": 11, "keep_valu": 11, "form_nam": 11, "hidden": [11, 15], "csrf_session": 11, "csrf_protection": 11, "lifespan": 11, "signing_inf": 11, "apag": 11, "disallow": 11, "renders": 11, "formstylebulm": [11, 13, 15], "formstylebootstrap4": 11, "grava\u00e7\u00f5": 11, "submet": 11, "validity": 11, "form_minimal": 11, "product_nam": 11, "product_quantity": 11, "not_accepted": 11, "form_exampl": 11, "intentionally": 11, "nor": 11, "management": 11, "form_basic": 11, "realnam": 11, "univers": 11, "dc": [11, 12], "comics": 11, "marvel": 11, "dual": 11, "explain": 11, "row": [11, 13, 15], "bottom": 11, "choos": [11, 12], "dropdown": [11, 13, 15], "prototyping": 11, "hand": [11, 15], "your_app": 11, "form_upload": 11, "required_fold": 11, "hashed": 11, "plugins": [11, 15], "checkboxwidget": 11, "datetimewidget": 11, "fileuploadwidget": 11, "listwidget": 11, "passwordwidget": 11, "radiowidget": 11, "selectwidget": 11, "textareawidget": 11, "improved": 11, "form_widgets": 11, "subclassing": 11, "improving": [11, 12], "form_custom_widgets": 11, "mycustomwidget": 11, "placehold": [11, 15], "no_tabl": [11, 15], "s_": [11, 15], "_placehold": [11, 15], "_titl": [11, 15], "_style": [11, 15], "mystyle": 11, "foreground": 11, "manipulated": 11, "manipulat": 11, "modify": [11, 13, 15], "granulary": 11, "submission": [11, 15], "inserting": 11, "begin": [11, 13], "detail_fields": 11, "frequently": 11, "redundancy": 11, "types": 11, "clash": 11, "isn": [11, 13], "ensur": 11, "surrounding": 11, "controls": 11, "correctly": 11, "stuff": [11, 13], "inject": [11, 16], "_onclick": 11, "doh": 11, "cancel": [11, 15], "attrs": [11, 13, 15], "history": 11, "drop": 11, "down": [11, 15], "lookups": 11, "selection": 11, "constructors": 11, "error_messag": 11, "fist": 11, "wrap": 11, "fill": [11, 15], "translation": [11, 14], "letters": 11, "numbers": 11, "alphanumeric": 11, "converts": 11, "us": 11, "phon": 11, "strict": 11, "fits": 11, "boundari": 11, "inputs": [11, 15], "maxsiz": 11, "255": 11, "minsiz": 11, "minimum": 11, "short": 11, "16": 11, "1kb": 11, "1mb": 11, "1048576": 11, "1024": 11, "cgi": 11, "fieldstorag": 11, "intuitively": 11, "rejects": 11, "breaks": 11, "syntactic": 11, "domain": [11, 12], "rfc": 11, "2616": 11, "semantic": [11, 12], "prepend": [11, 15], "front": [11, 15], "abbreviated": 11, "ca": 11, "2396": 11, "customizabl": [11, 13], "allowed_schem": 11, "exclud": 11, "lacking": 11, "rejected": 11, "prepended": [11, 14], "prepend_schem": 11, "prepending": 11, "internationalized": 11, "idn": 11, "3490": 11, "asci": 11, "punycod": 11, "3492": 11, "bit": 11, "beyond": 11, "standards": [11, 13], "hex": 11, "0x4e86": 11, "becom": 11, "4e": 11, "86": 11, "ftps": 11, "maxlen": 11, "slug": 11, "validated": 11, "repeated": 11, "dash": 11, "native_json": 11, "unchanged": 11, "hh": 11, "mm": 11, "ss": 11, "formats": 11, "yyyy": 11, "dd": 11, "symbols": 11, "symbol": 11, "year": [11, 15], "century": 11, "1963": 11, "day": 11, "month": 11, "28": 11, "08": 11, "aug": 11, "august": 11, "hour": 11, "24": 11, "clock": 11, "12": 11, "am": 11, "pm": 11, "minut": 11, "59": 11, "2008": 11, "2009": 11, "passwords": 11, "neith": 11, "strip": 11, "empty_regex": 11, "deprecated": [11, 15], "express": 11, "arrang": 11, "backward": 11, "compatibility": 11, "expressed": 11, "logical": 11, "terms": 11, "dot": 11, "fall": 11, "comparison": 11, "arithmetic": 11, "limits": 11, "internationaliz": 11, "decimals": 11, "floating": 11, "definit": 11, "ie": 11, "numerical": 11, "conversion": [11, 16], "filled": 11, "acceptanc": 11, "descriptiv": 11, "apple": 11, "banan": 11, "cherry": 11, "alphabetically": 11, "labels": 11, "sort": 11, "hulk": 11, "06": 11, "multiselect": 11, "exclusiv": [11, 12], "specials": 11, "forbidden": 11, "obviously": 11, "clev": 11, "enough": [11, 12], "cle": 11, "entropy": 11, "53": 11, "implicitly": 11, "performs": 11, "iterations": 11, "pbkdf2": 11, "byte": 11, "md5": 11, "hmac": 11, "thisisthekey": 11, "useless": [11, 14], "constant": 11, "mysaltvalu": 11, "somewhat": 11, "min_length": 11, "alg": 11, "consequently": 11, "invalidating": 11, "valued": 11, "emails": 11, "filter_in": 11, "split_emails": 11, "findall": 11, "mailt": 11, "customization": 11, "textar": 11, "sqlforms": 11, "blanks": 11, "operat": 11, "stand": 11, "onvalidation": 11, "accepts": 11, "emails_onvalidation": 11, "acts": 11, "attempted": 11, "through": 11, "dimensions": 11, "height": [11, 13, 15], "bmp": 11, "gif": 11, "jpeg": 11, "imaging": 11, "parts": 11, "taken": 11, "source1": 11, "extensions": 11, "lowercas": 11, "bypass": [11, 12], "200x200": 11, "pixels": 11, "extension": [11, 14], "crit": 11, "lastdot": 11, "separator": 11, "indicat": [11, 14], "tar": 11, "gz": 11, "insensitiv": 11, "thumbnail": 11, "jpg": 11, "older": 11, "regexlib": 11, "minip": 11, "maxip": 11, "is_localhost": 11, "is_privat": 11, "is_automatic": 11, "lowest": 11, "highest": 11, "flag": 11, "168": 11, "integers": 11, "boundary": 11, "199": 11, "forbid": 11, "meanings": 11, "169": 11, "254": 11, "network": [11, 12], "is_link_local": 11, "is_reserved": 11, "is_multicast": 11, "is_routeabl": 11, "is_6to4": 11, "is_tered": 11, "subnets": 11, "ipv6": 11, "allocated": 11, "networks": [11, 12], "fe80": 11, "ietf": 11, "multicast": 11, "ff00": 11, "6to4": 11, "2002": 11, "tered": 11, "2001": 11, "forcing": 11, "subnet": 11, "memb": [11, 12], "fb00": 11, "heading": 11, "blank": [11, 13], "removal": 11, "456": 11, "123456": 11, "synops": 11, "his": 11, "requirement": 11, "probability": 11, "rac": 11, "occurs": 11, "operationalerror": 11, "inserts": 11, "dbset": 11, "allowed_overrid": 11, "persons": 11, "days": 11, "registration_stamp": 11, "timedelt": 11, "value_field": 11, "representing_field": 11, "fourth": 11, "ten": 11, "selections": 11, "represented": 11, "orderby": [11, 12, 13, 15], "groupby": 11, "sorting": 11, "usefull": 11, "wishing": 11, "occasionally": 11, "_and": 11, "owners": 11, "subset": 11, "check_nonnegative_quantity": 11, "vital": 12, "multius": 12, "interchangeably": 12, "proc": 12, "confirms": 12, "say": 12, "guidelin": 12, "approv": 12, "step": [12, 14], "adiant": 12, "bloqu": 12, "nomeaplic": 12, "request_reset_password": 12, "reset_password": 12, "verify_email": 12, "sair": 12, "change_email": 12, "pr\u00f3pr": [12, 13], "allowed_actions": 12, "turn": [12, 13, 15], "verification": 12, "activated": 12, "successfully": 12, "challeng": 12, "submitting": 12, "correct": 12, "instantiation": [12, 13], "two_factor_filt": 12, "bypassed": 12, "sampl": 12, "user_outside_network": 12, "ipaddress": 12, "22": 12, "ip_list": 12, "ipv4network": 12, "ipv4address": 12, "mfa": 12, "sends": 12, "send_two_factor_email": 12, "from_address": 12, "youremail": 12, "flow": 12, "two_factor": 12, "endpoint": [12, 15], "_next_url": 12, "auth_plugins": 12, "hierarchical": 12, "saml": 12, "oauth": 12, "ui": 12, "adapt": 12, "exibi\u00e7\u00e3": [12, 13], "pam_plugin": 12, "pamplugin": 12, "register_plugin": 12, "vir": 12, "authenticat": 12, "directory": 12, "ldap_plugin": 12, "ldapplugin": 12, "ldap_setting": 12, "ad": 12, "base_dn": 12, "cn": 12, "ldap_settings": 12, "ubuntu": 12, "librari": 12, "apt": 12, "libldap2": 12, "libsasl2": 12, "oauth2googl": 12, "client_id": 12, "client_secret": 12, "callback_url": 12, "segred": 12, "oauth2facebook": 12, "oauth2discord": 12, "discord_client_id": 12, "discord_client_secret": 12, "uri": 12, "discriminator": 12, "mentioned": [12, 15], "verifying": 12, "existenc": 12, "showed": 12, "suffers": 12, "overkill": 12, "tagged_db": 12, "_tag": 12, "tagged_nam": 12, "danc": 12, "teach": 12, "auth_user_tagged_groups": 12, "chars": 12, "footing": 12, "authorized": 12, "displays": [12, 13], "not_authorized": 12, "find_by_tag": 12, "deix": 12, "exerc\u00edci": 12, "has_membership": 12, "school": 12, "physics": 12, "professor": 12, "ensin": 12, "m\u00e9di": 12, "f\u00edsic": 12, "colegial": 12, "creativity": 12, "auth_group": 12, "auth_groups": 12, "zapp": 12, "zap_id": 12, "zap": 12, "belongs": 12, "zapped": 12, "belong": [12, 14], "filtr": [12, 13], "membr": 12, "capabiliti": [13, 15], "corn": 13, "ston": 13, "exclus\u00e3": 13, "desc": 13, "search_queri": 13, "OU": 13, "search_form": 13, "p\u00f3s": 13, "substitui\u00e7\u00f5": 13, "jpsteil": 13, "grid_tutorial": 13, "advised": 13, "doubt": 13, "finding": 13, "precious": 13, "hints": 13, "gridclassstylebulm": 13, "grid_class_styl": 13, "gridclassstyl": 13, "val": 13, "perfectly": 13, "usabl": 13, "refresh": 13, "icons": 13, "showing": 13, "placing": 13, "maintainabl": 13, "lead": [13, 15], "advanced": [13, 16], "topics": [13, 16], "htmx": [13, 16], "forget": 13, "field_id": 13, "show_id": 13, "editabl": [13, 14], "pre_action_buttons": 13, "post_action_buttons": 13, "auto_process": [13, 15], "rows_per_pag": 13, "include_action_button_text": 13, "search_button_text": 13, "responsibl": 13, "especifiqu": 13, "action_button": 13, "imediat": 13, "estil": 13, "styling": 13, "styles": 13, "utilizing": 13, "during": 13, "involv": [13, 14], "fifth": 13, "clickabl": 13, "hid": 13, "modifi": 13, "handling": 13, "por\u00e7\u00f5": 13, "grelh": 13, "primarily": 13, "class_styl": 13, "unfortunately": 13, "bootstrap": 13, "gridactionbutton": 13, "additional_cl": 13, "additional_styl": 13, "override_cl": 13, "override_styl": 13, "append_id": 13, "ignore_attribute_plugin": 13, "naveg": 13, "clic": 13, "fa": 13, "calend": 13, "aul": 13, "mensag": 13, "verdad": 13, "id_field_nam": 13, "id_valu": 13, "querystring": 13, "defining": 13, "improvement": 13, "10px": 13, "foo": 13, "filter_out": 13, "foreign": 13, "company": 13, "is_null_or": 13, "empres": 13, "qued": 13, "empreg": 13, "joined": 13, "employe": 13, "joins": [13, 16], "clicked": [13, 15], "sorted": 13, "search_text": 13, "far": [13, 15], "grids": 13, "antig": 14, "similariti": 14, "identical": 14, "light": 14, "predetermined": 14, "debuggers": 14, "ides": 14, "constraining": 14, "mann": 14, "admin": 14, "locat": 14, "my_url_path": 14, "assumed": 14, "postfix": 14, "matters": 14, "expected": 14, "wrappers": 14, "hard": 14, "analogy": 14, "hous": 14, "preprocessing": 14, "postprocessing": 14, "carry": 14, "underlying": 14, "preguic": 14, "dangerous": 14, "pyweb": 14, "ability": 14, "extensibl": 14, "lacks": 14, "requires_": 14, "mean": 14, "establishing": 14, "attaching": 14, "labeling": 14, "assigning": 14, "rocket": 14, "301": 14, "sophisticated": 14, "dismissal": 14, "file_path": 14, "csv": 14, "app_fold": 14, "requires_login": 14, "user_email": 14, "retrieved": 14, "big": 14, "decorated": 14, "websocket": 15, "async": 15, "play": 15, "compliant": 15, "great": 15, "flexibility": 15, "react": 15, "angul": 15, "complexity": 15, "systems": 15, "reaping": 15, "benefits": 15, "ecosyst": 15, "difficult": 15, "road": 15, "reactivity": 15, "emerging": 15, "complexiti": 15, "technically": 15, "transitions": 15, "sockets": 15, "hypertext": 15, "cit1601": 15, "frontends": 15, "integration": 15, "coupl": 15, "hx": 15, "_hx": 15, "url_to_post_t": 15, "submitted": 15, "started": 15, "htmx_form_dem": 15, "htmx_list": 15, "htmx_form": 15, "cancel_attrs": 15, "sidec": 15, "unpkg": 15, "sh": 15, "functional": 15, "maintenanc": 15, "navigat": 15, "anchor": 15, "fancy": 15, "confirmation": 15, "htmx_grid": 15, "attributes_plugin": 15, "attributespluginhtmx": 15, "new_sidec": 15, "edit_sidec": 15, "confirmations": 15, "widgets": [15, 16], "dropdowns": 15, "functools": 15, "params": 15, "autocomplete_query": 15, "fk_tabl": 15, "ktabl": 15, "fk_field": 15, "kfield": 15, "_autocomplete_search_fields": 15, "sf": 15, "_search": 15, "len": 15, "data_label": 15, "htmxautocompletewidget": 15, "simple_query": 15, "pop": 15, "hold": 15, "hidden_input": 15, "hidden_div": 15, "keyup": 15, "delay": 15, "500ms": 15, "s_autocomplete_results": 15, "indicator": 15, "vals": 15, "search_valu": 15, "s_search": 15, "_autocomplet": 15, "onload": 15, "elt": 15, "queryselector": 15, "onkeydown": 15, "check_": 15, "s_down_key": 15, "keycod": 15, "s_autocomplet": 15, "focus": 15, "selectedindex": 15, "formatt": 15, "formstylefactory": 15, "class_inner_exceptions": 15, "vendor": 15, "vendor_typ": 15, "product_record": 15, "earli": 15, "whichev": 15, "wish": 15, "overriding": 15, "vendors": 15, "limiting": 15, "subqueri": 15, "clearly": 15, "prototyp": 15, "var": 15, "supporting": 15, "selected_elements": 15, "queryselectorall": 15, "array": 15, "eval": 15, "sandbox": 15, "math": 15, "wrapp": 15, "recereived": 15, "get_cooki": 15, "extracts": 15, "register_vue_component": 15, "lazily": 15, "res": 15, "upload_help": 15, "bind": 15, "my_id": 15, "reimplementation": 15, "basically": 15, "translat": 15, "clientsid": 15, "serversid": 15, "debounc": 15, "stepping": 15, "setinterval": 15, "500": 15, "skip": 15, "implementations": 15, "delaying": 15, "200ms": 15, "onclick": 15, "1000ms": 15, "tags_inputs": 15, "comm": 15, "browsers": 15, "jsl": 15, "tags_input": 15, "zip_cod": 15, "freetext": 15, "safar": 15, "edge": 15, "datalist": 15, "firfox": 15, "undocumented": 15, "padding": 15, "inlin": 15, "block": 15, "bord": 15, "radius": 15, "100px": 15, "111111": 15, "3em": 15, "2em": 15, "2px": 15, "opacity": 15, "capitaliz": 15, "tag_input": 15, "score_input": 15, "scor": 15, "new_password": 15, "poor": 15, "man": 15, "trapped": 15, "component_1": 15, "mycomponent": 15, "blink": 15, "loading": 15, "serving": 15, "envelop": 15, "moreov": 15, "other_pag": 15, "acknowledgments": 16, "dic": 16, "sugest\u00f5": 16, "contribu": 16, "coloc": 16, "understanding": 16, "plataform": 16, "proced": 16, "corr": 16, "installations": 16, "fixur": 16, "urlsign": 16, "caveats": 16, "memoiz": 16, "introduction": 16, "migra\u00e7\u00f5": 16, "raw": 16, "sql": 16, "Comando": 16, "computed": 16, "virtual": 16, "relations": 16, "export": 16, "caracter\u00edst": 16, "avanc": 16, "pegadinh": 16, "polic": 16, "overview": 16, "customizing": 16, "asynci": 16, "genindex": 16, "modindex": 16}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"O": [0, 1, 3, 4, 5, 6], "\u00e9": 0, "py4web": [0, 1, 6, 14, 15, 16], "acknowledgments": 0, "ajud": 1, "recurs": [1, 6], "dic": 1, "Este": 1, "manual": [1, 16], "grup": 1, "googl": [1, 2, 6, 12], "the": [1, 2, 4, 5, 6, 7, 11, 13, 15], "discord": [1, 12], "serv": [1, 5, 6, 9], "tutori": 1, "v\u00edd": 1, "As": 1, "font": [1, 2], "github": 1, "sugest\u00f5": 1, "pr\u00e9": [1, 2], "requisit": [1, 2], "Um": [1, 6], "local": [1, 2, 6], "trabalh": 1, "python": 1, "modern": 1, "depur": 1, "vscod": 1, "pycharm": 1, "Como": 1, "contribu": 1, "instal": 2, "coloc": 2, "funcion": 2, "understanding": 2, "design": [2, 11], "plataform": 2, "suport": [2, 6], "proced": 2, "configur": [2, 6], "part": [2, 6], "bin\u00e1ri": 2, "pip": 2, "installing": 2, "using": [2, 5, 8, 9, 12, 13, 15], "virtual": [2, 6], "environment": 2, "global": 2, "melhor": 2, "primeir": [2, 4], "corr": 2, "op\u00e7\u00f5": 2, "linh": 2, "comando": 2, "op\u00e7\u00e3": 2, "call": 2, "new_app": 2, "run": 2, "set_password": 2, "setup": 2, "shell": [2, 6], "version": [2, 6], "special": [2, 11], "installations": 2, "https": 2, "wsgi": 2, "deployment": 2, "on": 2, "gcloud": 2, "aka": 2, "gae": 2, "app": [2, 4], "engin": 2, "implant": 2, "pythonanywher": 2, "dock": 2, "podman": 2, "ubuntu": 2, "dashboard": 3, "A": [3, 6, 9, 11], "p\u00e1gin": [3, 4], "web": [3, 4], "principal": 3, "sess\u00e3": 3, "cri": 4, "aplic": [4, 6], "Do": 4, "princ\u00edpi": 4, "est\u00e1t": 4, "din\u00e2m": 4, "Em": 4, "valor": [4, 6], "retorn": [4, 6], "rot": 4, "objet": 4, "request": 4, "model": [4, 6], "_scaffold": 4, "copying": 4, "watch": 4, "fil": [4, 11, 14], "chang": 4, "fixur": 5, "fixtur": 5, "templat": [5, 8, 13], "inject": [5, 9], "translator": 5, "flash": [5, 14], "session": 5, "client": 5, "sid": [5, 9], "in": [5, 8, 9, 15], "cooki": 5, "memcach": 5, "red": [5, 13], "databas": [5, 6, 11], "anywher": 5, "sharing": 5, "sessions": 5, "condition": 5, "urlsign": 5, "dal": [5, 6], "auth": [5, 12, 14], "caveats": 5, "about": 5, "personaliz": [5, 6, 9, 13], "multipl": [5, 12], "caching": 5, "memoiz": 5, "decor": 5, "convenient": 5, "abstraction": 6, "lay": 6, "introduction": 6, "supported": 6, "quick": 6, "tour": 6, "usand": [6, 13], "stand": 6, "alon": 6, "experiment": 6, "construtor": 6, "assinatur": 6, "strings": 6, "conex\u00e3": 6, "par\u00e2metr": 6, "uri": 6, "pool": 6, "conex\u00f5": 6, "falh": 6, "tentat": 6, "tabel": [6, 16], "preguic": 6, "less": 6, "banc": 6, "dad": 6, "replic": 6, "palavr": 6, "chav": 6, "reserv": 6, "quoting": 6, "cas": 6, "faz": 6, "segur": 6, "outr": 6, "past": 6, "padr\u00e3": 6, "migra\u00e7\u00e3": 6, "commit": 6, "rollback": 6, "tabl": [6, 9], "define_tabl": 6, "id": 6, "not": 6, "sobr": 6, "prim\u00e1r": 6, "plural": 6, "singul": 6, "redefin": 6, "format": [6, 11, 15], "represent": 6, "fich": 6, "rnam": 6, "nom": 6, "real": 6, "primarykey": 6, "par": [6, 8, 14], "leg": 6, "migrat": 6, "fake_migrat": 6, "table_class": 6, "sequence_nam": 6, "trigger_nam": 6, "polymodel": 6, "on_defin": 6, "adicion": 6, "atribut": 6, "camp": [6, 13], "field": [6, 11], "types": 6, "and": [6, 7, 8, 11, 12, 13, 14, 15], "validators": [6, 11], "modific": 6, "temp": 6, "execu": 6, "Mais": 6, "envi": 6, "migra\u00e7\u00f5": 6, "fixa\u00e7\u00e3": 6, "quebr": 6, "resum": 6, "control": 6, "methods": [6, 14], "insert": 6, "query": 6, "set": [6, 11], "rows": 6, "update_or_insert": 6, "validate_and_insert": 6, "validate_and_updat": 6, "drop": 6, "marca\u00e7\u00e3": 6, "registr": 6, "raw": 6, "sql": 6, "executesql": 6, "_lastsql": 6, "temporiz": 6, "consult": 6, "\u00edndic": [6, 16], "generating": 6, "Comando": 6, "select": [6, 9], "selet": 6, "uso": 6, "mem\u00f3r": 6, "inferior": 6, "bas": 6, "iter": 6, "renderiz": 6, "atalh": 6, "obten\u00e7\u00e3": 6, "row": 6, "s": 6, "orderby": 6, "groupby": 6, "limitby": 6, "distinct": 6, "having": 6, "orderby_on_limitby": 6, "join": 6, "left": 6, "cach": 6, "orden": 6, "tend": 6, "distint": 6, "junt": 6, "deix": 6, "oper": 6, "l\u00f3gic": 6, "count": [6, 14], "isempty": 6, "delet": 6, "updat": 6, "express\u00f5": 6, "update_record": 6, "inser": 6, "atualiz": [6, 10], "dicion\u00e1ri": 6, "first": 6, "last": 6, "as_dict": 6, "as_list": 6, "combin": 6, "find": [6, 9], "exclud": 6, "sort": 6, "selects": 6, "computed": 6, "fields": 6, "comput": 6, "virtu": 6, "nov": 6, "estil": 6, "experimental": 6, "velh": 6, "antig": 6, "joins": 6, "relations": 6, "muit": 6, "rela\u00e7\u00e3": 6, "inner": 6, "out": 6, "agrup": 6, "cont": 6, "many": 6, "to": 6, "relation": 6, "aut": 6, "referent": [6, 13, 16], "alias": 6, "lik": 6, "ilik": 6, "regexp": 6, "startswith": 6, "endswith": 6, "contains": 6, "upper": 6, "low": 6, "year": 6, "month": 6, "day": 6, "hour": 6, "minut": 6, "seconds": 6, "belongs": 6, "sum": 6, "avg": 6, "min": 6, "max": 6, "len": 6, "substrings": 6, "Os": [6, 13], "defeit": 6, "coalesc": 6, "coalesce_zer": 6, "export": 6, "import": 6, "csv": 6, "cad": 6, "vez": 6, "tod": 6, "mesm": 6, "sincroniz": 6, "remot": 6, "html": [6, 9], "xml": [6, 9], "caracter\u00edst": 6, "avanc": 6, "list": 6, "type": [6, 11], "heranc": 6, "filter_in": 6, "filter_out": 6, "cham": 6, "inser\u00e7\u00e3": 6, "exclus\u00e3": 6, "cascad": 6, "record": 6, "filtr": 6, "comuns": 6, "tip": 6, "defin": 6, "transa\u00e7\u00e3": 6, "distribu\u00edd": 6, "copi": 6, "db": 6, "pegadinh": 6, "adapt": 6, "sqlit": 6, "mysql": 6, "mssql": 6, "microsoft": 6, "or\u00e1cul": 6, "nosql": 6, "datastor": 6, "restap": 7, "polic": 7, "actions": [7, 12], "get": 7, "practical": 7, "exampl": [7, 11, 13, 14, 15], "respons": 7, "linguag": 8, "yatl": [8, 9], "sintax": 8, "b\u00e1sic": 8, "whil": 8, "if": 8, "elif": 8, "else": 8, "tent": 8, "excet": 8, "finally": 8, "def": 8, "return": [8, 14], "information": 8, "workflow": 8, "extend": 8, "includ": 8, "extending": 8, "variabl": [8, 14], "functions": [8, 11], "block": 8, "sup": 8, "pag": 8, "layout": 8, "standard": [8, 11], "structur": [8, 11], "default": 8, "mobil": 8, "development": 8, "helpers": 9, "overview": 9, "built": 9, "body": 9, "cat": 9, "div": 9, "EM": 9, "form": [9, 11, 14, 15], "h1": 9, "h2": 9, "h3": 9, "h4": 9, "h5": 9, "h6": 9, "head": 9, "i": 9, "img": 9, "input": 9, "label": 9, "li": 9, "ol": 9, "option": 9, "p": 9, "pre": 9, "script": 9, "span": 9, "style": [9, 13], "tr": 9, "td": 9, "tbody": 9, "textar": 9, "th": 9, "thead": 9, "titl": 9, "tt": 9, "ul": 9, "url": 9, "tag": 9, "beautify": 9, "dom": 9, "children": 9, "internacionaliz": 10, "pluraliz": 10, "arquiv": 10, "tradu\u00e7\u00e3": 10, "foruml\u00e1ri": 11, "constructor": 11, "minimal": 11, "without": 11, "basic": [11, 13], "upload": 11, "widgets": 11, "custom": [11, 13], "advanced": [11, 15], "manipulation": 11, "forms": 11, "sidec": 11, "paramet": 11, "valid": 11, "formul\u00e1ri": 11, "text": 11, "is_alphanumeric": 11, "is_low": 11, "is_upp": 11, "is_email": 11, "is_match": 11, "is_length": 11, "is_url": 11, "is_slug": 11, "is_json": 11, "dat": 11, "tim": 11, "is_tim": 11, "is_dat": 11, "is_datetim": 11, "is_date_in_rang": 11, "is_datetime_in_rang": 11, "rang": 11, "equality": 11, "is_equal_t": 11, "is_not_empty": 11, "is_null_or": 11, "is_empty_or": 11, "is_expr": 11, "is_decimal_in_rang": 11, "is_float_in_rang": 11, "is_int_in_rang": 11, "is_in_set": 11, "checkbox": 11, "validation": 11, "dictionari": 11, "tupl": 11, "with": [11, 12, 14], "sorted": 11, "options": 11, "tagging": 11, "complexity": 11, "security": 11, "is_strong": 11, "crypt": 11, "is_list_of": 11, "is_list_of_emails": 11, "any_of": 11, "is_imag": 11, "is_fil": 11, "is_upload_filenam": 11, "is_ipv4": 11, "is_ipv6": 11, "is_ipaddress": 11, "other": 11, "cleanup": 11, "is_not_in_db": 11, "is_in_db": 11, "authentication": 12, "authorization": 12, "interfac": 12, "autentic": 12, "insid": 12, "two": 12, "factor": 12, "two_factor_required": 12, "two_factor_send": 12, "two_factor_tri": 12, "plugins": 12, "pam": 12, "ldap": 12, "oauth2": 12, "facebook": 12, "tags": 12, "etiquet": 12, "permiss\u00f5": 12, "objects": 12, "key": 13, "featur": 13, "grid": [13, 14, 15], "object": [13, 15], "searching": 13, "filtering": 13, "crud": 13, "settings": 13, "columns": 13, "customizing": 13, "a\u00e7\u00e3": 13, "bot\u00f5": 13, "bot\u00e3": 13, "class": 13, "amostr": 13, "callabl": 13, "parameters": 13, "De": 14, "web2py": 14, "simpl": 14, "conversion": 14, "hell": 14, "world": 14, "redirect": 14, "returning": 14, "args": 14, "calling": 14, "setting": 14, "up": 14, "view": 14, "accessing": 14, "OS": 14, "topics": 15, "asynci": 15, "htmx": 15, "usag": 15, "autocomplet": 15, "widget": 15, "utils": 15, "js": 15, "string": 15, "q": 15, "t": 15, "conte\u00fad": 16}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"O que \u00e9 py4web?": [[0, "what-is-py4web"]], "Acknowledgments": [[0, "acknowledgments"]], "Ajuda, recursos e dicas": [[1, "help-resources-and-hints"]], "Recursos": [[1, "resources"]], "Este manual": [[1, "this-manual"]], "O grupo Google": [[1, "the-google-group"]], "The Discord server": [[1, "the-discord-server"]], "Tutoriais e v\u00eddeo": [[1, "tutorials-and-video"]], "As fontes no GitHub": [[1, "the-sources-on-github"]], "Dicas e sugest\u00f5es": [[1, "hints-and-tips"]], "Pr\u00e9-requisitos": [[1, "prerequisites"]], "Um local de trabalho python moderna": [[1, "a-modern-python-workplace"]], "Depura\u00e7\u00e3o py4web com VScode": [[1, "debugging-py4web-with-vscode"]], "Depura\u00e7\u00e3o py4web com PyCharm": [[1, "debugging-py4web-with-pycharm"]], "Como contribuir": [[1, "how-to-contribute"]], "Instala\u00e7\u00e3o e coloca\u00e7\u00e3o em funcionamento": [[2, "installation-and-startup"]], "Understanding the design": [[2, "understanding-the-design"]], "Plataformas e pr\u00e9-requisitos suportados": [[2, "supported-platforms-and-prerequisites"]], "Procedimentos de configura\u00e7\u00e3o": [[2, "setup-procedures"]], "Instalando a partir de bin\u00e1rios": [[2, "installing-from-binaries"]], "Instalando a partir de pip": [[2, "installing-from-pip"]], "Installing using a virtual environment": [[2, "installing-using-a-virtual-environment"]], "Instala\u00e7\u00e3o de fonte (globalmente)": [[2, "installing-from-source-globally"]], "Instalando a partir de fonte (localmente)": [[2, "installing-from-source-locally"]], "Melhoramento": [[2, "upgrading"]], "Primeira corrida": [[2, "first-run"]], "Op\u00e7\u00f5es de linha de comando": [[2, "command-line-options"]], "Op\u00e7\u00e3o `` comando call``": [[2, "call-command-option"]], "Op\u00e7\u00e3o `` comando new_app``": [[2, "new-app-command-option"]], "Op\u00e7\u00e3o `` comando run``": [[2, "run-command-option"]], "Op\u00e7\u00e3o `` comando set_password``": [[2, "set-password-command-option"]], "Op\u00e7\u00e3o `` comando setup``": [[2, "setup-command-option"]], "Op\u00e7\u00e3o `` comando shell``": [[2, "shell-command-option"]], "Op\u00e7\u00e3o `` comando version``": [[2, "version-command-option"]], "Special installations": [[2, "special-installations"]], "HTTPS": [[2, "https"]], "WSGI": [[2, "wsgi"]], "Deployment on GCloud (aka GAE - Google App Engine)": [[2, "deployment-on-gcloud-aka-gae-google-app-engine"]], "Implanta\u00e7\u00e3o em PythonAnywhere.com": [[2, "deployment-on-pythonanywhere-com"]], "Deployment on Docker/Podman": [[2, "deployment-on-docker-podman"]], "Deployment on Ubuntu": [[2, "deployment-on-ubuntu"]], "O Dashboard": [[3, "the-dashboard"]], "A p\u00e1gina Web principal": [[3, "the-main-web-page"]], "Sess\u00e3o no Dashboard": [[3, "login-into-the-dashboard"]], "Criando seu primeiro aplicativo": [[4, "creating-your-first-app"]], "Do princ\u00edpio": [[4, "from-scratch"]], "P\u00e1ginas est\u00e1ticas": [[4, "static-web-pages"]], "P\u00e1ginas web din\u00e2micas": [[4, "dynamic-web-pages"]], "Em valores de retorno": [[4, "on-return-values"]], "Rotas": [[4, "routes"]], "O objeto `` request``": [[4, "the-request-object"]], "Modelos": [[4, "templates"]], "The _scaffold app": [[4, "the-scaffold-app"]], "Copying the _scaffold app": [[4, "copying-the-scaffold-app"]], "Watch for files change": [[4, "watch-for-files-change"]], "Fixures": [[5, "fixtures"]], "Using Fixtures": [[5, "using-fixtures"]], "The Template fixture": [[5, "the-template-fixture"]], "The Inject fixture": [[5, "the-inject-fixture"]], "The Translator fixture": [[5, "the-translator-fixture"]], "O fixture flash": [[5, "the-flash-fixture"]], "The Session fixture": [[5, "the-session-fixture"]], "Client-side session in cookies": [[5, "client-side-session-in-cookies"]], "Server-side session in memcache": [[5, "server-side-session-in-memcache"]], "Server-side session in Redis": [[5, "server-side-session-in-redis"]], "Server-side session in database": [[5, "server-side-session-in-database"]], "Server-side session anywhere": [[5, "server-side-session-anywhere"]], "Sharing sessions": [[5, "sharing-sessions"]], "The Condition fixture": [[5, "the-condition-fixture"]], "The URLsigner fixture": [[5, "the-urlsigner-fixture"]], "O fixture DAL": [[5, "the-dal-fixture"]], "The Auth fixture": [[5, "the-auth-fixture"]], "Caveats about fixtures": [[5, "caveats-about-fixtures"]], "Fixtures personalizados": [[5, "custom-fixtures"]], "Multiple fixtures": [[5, "multiple-fixtures"]], "Caching e Memoize": [[5, "caching-and-memoize"]], "Decoradores de conveni\u00eancia": [[5, "convenience-decorators"]], "The Database Abstraction Layer (DAL)": [[6, "the-database-abstraction-layer-dal"]], "DAL introduction": [[6, "dal-introduction"]], "py4web model": [[6, "py4web-model"]], "Supported databases": [[6, "supported-databases"]], "The DAL: a quick tour": [[6, "the-dal-a-quick-tour"]], "Usando o DAL \u201cstand-alone\u201d": [[6, "using-the-dal-stand-alone"]], "Experimentar com o shell py4web": [[6, "experiment-with-the-py4web-shell"]], "Construtor DAL": [[6, "dal-constructor"]], "Assinatura da DAL": [[6, "dal-signature"]], "Strings de conex\u00e3o (o par\u00e2metro uri)": [[6, "connection-strings-the-uri-parameter"]], "O pool de conex\u00f5es": [[6, "connection-pooling"]], "Falhas de conex\u00e3o (par\u00e2metro tentativas)": [[6, "connection-failures-attempts-parameter"]], "Tabelas pregui\u00e7osos": [[6, "lazy-tables"]], "Aplicativos de modelo-less": [[6, "model-less-applications"]], "Bancos de dados replicados": [[6, "replicated-databases"]], "Palavras-chave reservadas": [[6, "reserved-keywords"]], "Configura\u00e7\u00f5es de quoting e case e do banco de dados": [[6, "database-quoting-and-case-settings"]], "Fazendo uma conex\u00e3o segura": [[6, "making-a-secure-connection"]], "Outros par\u00e2metros do construtor DAL": [[6, "other-dal-constructor-parameters"]], "Local de pasta do banco de dados": [[6, "database-folder-location"]], "Configura\u00e7\u00f5es padr\u00e3o de migra\u00e7\u00e3o": [[6, "default-migration-settings"]], "`` `` commit`` e rollback``": [[6, "commit-and-rollback"]], "Construtor Table": [[6, "table-constructor"]], "assinatura define_table": [[6, "define-table-signature"]], "`` Id``: Notas sobre a chave prim\u00e1ria": [[6, "id-notes-about-the-primary-key"]], "`` `` Plural`` e singular``": [[6, "plural-and-singular"]], "`` Redefine``": [[6, "redefine"]], "`` Format``: representa\u00e7\u00e3o da ficha": [[6, "format-record-representation"]], "`` Rname``: nome real": [[6, "rname-real-name"]], "`` Primarykey``: Suporte para tabelas legadas": [[6, "primarykey-support-for-legacy-tables"]], "`` Migrate``, `` fake_migrate``": [[6, "migrate-fake-migrate"]], "`` Table_class``": [[6, "table-class"]], "`` Sequence_name``": [[6, "sequence-name"]], "`` Trigger_name``": [[6, "trigger-name"]], "`` polymodel``": [[6, "polymodel"]], "`` On_define``": [[6, "on-define"]], "Adicionando atributos para campos e tabelas": [[6, "adding-attributes-to-fields-and-tables"]], "Bancos de dados legados e tabelas com chave": [[6, "legacy-databases-and-keyed-tables"]], "Construtor Field": [[6, "field-constructor"]], "Field types and validators": [[6, "field-types-and-validators"]], "modifica\u00e7\u00e3o da tabela e campo em tempo de execu\u00e7\u00e3o": [[6, "run-time-field-and-table-modification"]], "Mais sobre envios": [[6, "more-on-uploads"]], "Migra\u00e7\u00f5es": [[6, "migrations"]], "Fixa\u00e7\u00e3o migra\u00e7\u00f5es quebrados": [[6, "fixing-broken-migrations"]], "Migra\u00e7\u00e3o resumo controle": [[6, "migration-control-summary"]], "Table methods": [[6, "table-methods"]], "`` Insert``": [[6, "insert"]], "`` Query``, `` Set``, `` Rows``": [[6, "query-set-rows"]], "`` Update_or_insert``": [[6, "update-or-insert"]], "`` Validate_and_insert``, `` validate_and_update``": [[6, "validate-and-insert-validate-and-update"]], "`` Drop``": [[6, "drop"]], "Marca\u00e7\u00e3o de registros": [[6, "tagging-records"]], "Raw SQL": [[6, "raw-sql"]], "`` executesql``": [[6, "executesql"]], "`` _Lastsql``": [[6, "lastsql"]], "Temporiza\u00e7\u00e3o de consultas": [[6, "timing-queries"]], "\u00cdndices": [[6, "indexes"]], "Generating raw SQL": [[6, "generating-raw-sql"]], "`` Comando SELECT``": [[6, "select-command"]], "Usando um seleto para uso de mem\u00f3ria inferior \u00e0 base de iterador": [[6, "using-an-iterator-based-select-for-lower-memory-use"]], "Renderizando Rows com represent": [[6, "rendering-rows-using-represent"]], "Atalhos": [[6, "shortcuts"]], "A obten\u00e7\u00e3o de um `` row``": [[6, "fetching-a-row"]], "Recursivas `` s SELECT``": [[6, "recursive-selects"]], "`` Orderby``, `` groupby``, `` limitby``, `` distinct``, `` having``, `` orderby_on_limitby``, `` join``, `` left``, `` cache``": [[6, "orderby-groupby-limitby-distinct-having-orderby-on-limitby-join-left-cache"]], "ordenar por": [[6, "orderby"]], "groupby, tendo": [[6, "groupby-having"]], "distinto": [[6, "distinct"]], "limitby": [[6, "limitby"]], "orderby_on_limitby": [[6, "orderby-on-limitby"]], "juntar-se, deixou": [[6, "join-left"]], "cache, em cache": [[6, "cache-cacheable"]], "Operadores l\u00f3gicos": [[6, "logical-operators"]], "`` Count``, `` isempty``, `` DELETE``, `` update``": [[6, "count-isempty-delete-update"]], "Express\u00f5es": [[6, "expressions"]], "`` case``": [[6, "case"]], "`` Update_record``": [[6, "update-record"]], "Inserir e atualizar a partir de um dicion\u00e1rio": [[6, "inserting-and-updating-from-a-dictionary"]], "`` `` First`` e last``": [[6, "first-and-last"]], "`` `` As_dict`` e as_list``": [[6, "as-dict-and-as-list"]], "Combinando Rows": [[6, "combining-rows"]], "`` Find``, `` exclude``, `` sort``": [[6, "find-exclude-sort"]], "Selects com cache": [[6, "caching-selects"]], "Computed and Virtual fields": [[6, "computed-and-virtual-fields"]], "Campos computados": [[6, "computed-fields"]], "Campos virtuais": [[6, "virtual-fields"]], "Campos virtuais novo estilo (experimental)": [[6, "new-style-virtual-fields-experimental"]], "Campos virtuais velho antigo": [[6, "old-style-virtual-fields"]], "Joins and Relations": [[6, "joins-and-relations"]], "Um para muitos rela\u00e7\u00e3o": [[6, "one-to-many-relation"]], "Inner join": [[6, "inner-join"]], "Left outer join": [[6, "left-outer-join"]], "Agrupamento e contando": [[6, "grouping-and-counting"]], "Many to many relation": [[6, "many-to-many-relation"]], "A auto-refer\u00eancia e aliases": [[6, "self-reference-and-aliases"]], "Outros operadores": [[6, "other-operators"]], "`` Like``, `` ilike``, `` regexp``, `` startswith``, `` endswith``, `` contains``, `` upper``, `` lower``": [[6, "like-ilike-regexp-startswith-endswith-contains-upper-lower"]], "`` Year``, `` month``, `` day``, `` hour``, `` minutes``, `` seconds``": [[6, "year-month-day-hour-minutes-seconds"]], "`` Belongs``": [[6, "belongs"]], "`` Sum``, `` avg``, `` min``, `` `` max`` e len``": [[6, "sum-avg-min-max-and-len"]], "Substrings": [[6, "substrings"]], "Os valores por defeito com `` `` coalesce`` e coalesce_zero``": [[6, "default-values-with-coalesce-and-coalesce-zero"]], "Exportar e importar dados": [[6, "exporting-and-importing-data"]], "CSV (uma tabela de cada vez)": [[6, "csv-one-table-at-a-time"]], "CSV (todas as tabelas ao mesmo tempo)": [[6, "csv-all-tables-at-once"]], "CSV e sincroniza\u00e7\u00e3o de banco de dados remoto": [[6, "csv-and-remote-database-synchronization"]], "HTML e XML (uma tabela de cada vez)": [[6, "html-and-xml-one-table-at-a-time"]], "Representa\u00e7\u00e3o de dados": [[6, "data-representation"]], "Caracter\u00edsticas avan\u00e7adas": [[6, "advanced-features"]], "`` Lista: `` e `` contains``": [[6, "list-type-and-contains"]], "Heran\u00e7a de tabela": [[6, "table-inheritance"]], "`` `` Filter_in`` e filter_out``": [[6, "filter-in-and-filter-out"]], "retornos de chamada no registro de inser\u00e7\u00e3o, exclus\u00e3o e atualiza\u00e7\u00e3o": [[6, "callbacks-on-record-insert-delete-and-update"]], "Cascades no banco de dados": [[6, "database-cascades"]], "versionamento recorde": [[6, "record-versioning"]], "filtros comuns": [[6, "common-filters"]], "Personalizados `` tipos Field``": [[6, "custom-field-types"]], "Usando DAL sem definir tabelas": [[6, "using-dal-without-define-tables"]], "Transa\u00e7\u00e3o distribu\u00edda": [[6, "distributed-transaction"]], "Copiar dados de um para outro db": [[6, "copy-data-from-one-db-into-another"]], "Pegadinhas": [[6, "gotchas"]], "Nota sobre novo DAL e adaptadores": [[6, "note-on-new-dal-and-adapters"]], "SQLite": [[6, "sqlite"]], "MySQL": [[6, "mysql"]], "Google SQL": [[6, "google-sql"]], "MSSQL (Microsoft SQL Server)": [[6, "mssql-microsoft-sql-server"]], "Or\u00e1culo": [[6, "oracle"]], "Google NoSQL (Datastore)": [[6, "google-nosql-datastore"]], "The RestAPI": [[7, "the-restapi"]], "RestAPI policies and actions": [[7, "restapi-policies-and-actions"]], "RestAPI GET": [[7, "restapi-get"]], "RestAPI practical examples": [[7, "restapi-practical-examples"]], "The RestAPI response": [[7, "the-restapi-response"]], "Linguagem de template YATL": [[8, "yatl-template-language"]], "Sintaxe b\u00e1sica": [[8, "basic-syntax"]], "`` Para \u2026 in``": [[8, "for-in"]], "`` While``": [[8, "while"]], "`` If \u2026 elif \u2026 else``": [[8, "if-elif-else"]], "`` Tentar \u2026 exceto \u2026 else \u2026 finally``": [[8, "try-except-else-finally"]], "`` Def \u2026 return``": [[8, "def-return"]], "Information workflow": [[8, "information-workflow"]], "extend and include": [[8, "extend-and-include"]], "Extending using variables": [[8, "extending-using-variables"]], "Template Functions": [[8, "template-functions"]], "block and super": [[8, "block-and-super"]], "Page layout standard structure": [[8, "page-layout-standard-structure"]], "Default page layout": [[8, "default-page-layout"]], "Mobile development": [[8, "mobile-development"]], "Helpers YATL": [[9, "yatl-helpers"]], "Helpers overview": [[9, "helpers-overview"]], "Built-in helpers": [[9, "built-in-helpers"]], "`` XML``": [[9, "xml"]], "`` A``": [[9, "a"]], "`` BODY``": [[9, "body"]], "`` CAT``": [[9, "cat"]], "`` Div``": [[9, "div"]], "`` EM``": [[9, "em"]], "`` Form``": [[9, "form"]], "`` H1``, `` h2``, `` H3``, `` H4``, `` H5``, `` H6``": [[9, "h1-h2-h3-h4-h5-h6"]], "`` HEAD``": [[9, "head"]], "`` HTML``": [[9, "html"]], "`` I``": [[9, "i"]], "`` IMG``": [[9, "img"]], "`` INPUT``": [[9, "input"]], "`` Label``": [[9, "label"]], "`` LI``": [[9, "li"]], "`` OL``": [[9, "ol"]], "`` OPTION``": [[9, "option"]], "`` P``": [[9, "p"]], "`` PRE``": [[9, "pre"]], "`` SCRIPT``": [[9, "script"]], "`` SELECT``": [[9, "select"]], "`` SPAN``": [[9, "span"]], "`` STYLE``": [[9, "style"]], "`` TABLE``, `` TR``, `` TD``": [[9, "table-tr-td"]], "`` TBODY``": [[9, "tbody"]], "`` TEXTAREA``": [[9, "textarea"]], "`` TH``": [[9, "th"]], "`` THEAD``": [[9, "thead"]], "`` TITLE``": [[9, "title"]], "`` TT``": [[9, "tt"]], "`` UL``": [[9, "ul"]], "`` URL``": [[9, "url"]], "Helpers personalizados": [[9, "custom-helpers"]], "`` TAG``": [[9, "tag"]], "`` BEAUTIFY``": [[9, "beautify"]], "Server-side DOM": [[9, "server-side-dom"]], "children": [[9, "children"]], "find": [[9, "find"]], "Using Inject": [[9, "using-inject"]], "Internacionaliza\u00e7\u00e3o": [[10, "internationalization"]], "Pluralizar": [[10, "pluralize"]], "Atualizar os arquivos de tradu\u00e7\u00e3o": [[10, "update-the-translation-files"]], "Foruml\u00e1rios": [[11, "forms"]], "The Form constructor": [[11, "the-form-constructor"]], "A minimal form example without a database": [[11, "a-minimal-form-example-without-a-database"]], "Basic form example": [[11, "basic-form-example"]], "File upload field": [[11, "file-upload-field"]], "Widgets": [[11, "widgets"]], "Standard widgets": [[11, "standard-widgets"]], "Custom widgets": [[11, "custom-widgets"]], "Advanced form design": [[11, "advanced-form-design"]], "Form structure manipulation": [[11, "form-structure-manipulation"]], "Custom forms": [[11, "custom-forms"]], "The sidecar parameter": [[11, "the-sidecar-parameter"]], "Valida\u00e7\u00e3o de formul\u00e1rio": [[11, "form-validation"]], "Text format validators": [[11, "text-format-validators"]], "IS_ALPHANUMERIC": [[11, "is-alphanumeric"]], "IS_LOWER": [[11, "is-lower"]], "IS_UPPER": [[11, "is-upper"]], "IS_EMAIL": [[11, "is-email"]], "IS_MATCH": [[11, "is-match"]], "IS_LENGTH": [[11, "is-length"]], "IS_URL": [[11, "is-url"]], "IS_SLUG": [[11, "is-slug"]], "IS_JSON": [[11, "is-json"]], "Date and time validators": [[11, "date-and-time-validators"]], "IS_TIME": [[11, "is-time"]], "IS_DATE": [[11, "is-date"]], "IS_DATETIME": [[11, "is-datetime"]], "IS_DATE_IN_RANGE": [[11, "is-date-in-range"]], "IS_DATETIME_IN_RANGE": [[11, "is-datetime-in-range"]], "Range, set and equality validators": [[11, "range-set-and-equality-validators"]], "IS_EQUAL_TO": [[11, "is-equal-to"]], "IS_NOT_EMPTY": [[11, "is-not-empty"]], "IS_NULL_OR": [[11, "is-null-or"]], "IS_EMPTY_OR": [[11, "is-empty-or"]], "IS_EXPR": [[11, "is-expr"]], "IS_DECIMAL_IN_RANGE": [[11, "is-decimal-in-range"]], "IS_FLOAT_IN_RANGE": [[11, "is-float-in-range"]], "IS_INT_IN_RANGE": [[11, "is-int-in-range"]], "IS_IN_SET": [[11, "is-in-set"]], "Checkbox validation": [[11, "checkbox-validation"]], "Dictionaries and tuples with IS_IN_SET": [[11, "dictionaries-and-tuples-with-is-in-set"]], "Sorted options": [[11, "sorted-options"]], "IS_IN_SET and Tagging": [[11, "is-in-set-and-tagging"]], "Complexity and security validators": [[11, "complexity-and-security-validators"]], "IS_STRONG": [[11, "is-strong"]], "CRYPT": [[11, "crypt"]], "Special type validators": [[11, "special-type-validators"]], "IS_LIST_OF": [[11, "is-list-of"]], "IS_LIST_OF_EMAILS": [[11, "is-list-of-emails"]], "ANY_OF": [[11, "any-of"]], "IS_IMAGE": [[11, "is-image"]], "IS_FILE": [[11, "is-file"]], "IS_UPLOAD_FILENAME": [[11, "is-upload-filename"]], "IS_IPV4": [[11, "is-ipv4"]], "IS_IPV6": [[11, "is-ipv6"]], "IS_IPADDRESS": [[11, "is-ipaddress"]], "Other validators": [[11, "other-validators"]], "CLEANUP": [[11, "cleanup"]], "Database validators": [[11, "database-validators"]], "IS_NOT_IN_DB": [[11, "is-not-in-db"]], "IS_IN_DB": [[11, "is-in-db"]], "IS_IN_DB and Tagging": [[11, "is-in-db-and-tagging"]], "Validation functions": [[11, "validation-functions"]], "Authentication and authorization": [[12, "authentication-and-authorization"]], "Authentication using Auth": [[12, "authentication-using-auth"]], "Interface de autentica\u00e7\u00e3o": [[12, "auth-ui"]], "Using Auth inside actions": [[12, "using-auth-inside-actions"]], "Two Factor Authentication": [[12, "two-factor-authentication"]], "two_factor_required": [[12, "two-factor-required"]], "two_factor_send": [[12, "two-factor-send"]], "two_factor_tries": [[12, "two-factor-tries"]], "Plugins de Autentica\u00e7\u00e3o": [[12, "auth-plugins"]], "PAM": [[12, "pam"]], "LDAP": [[12, "ldap"]], "OAuth2 with Google": [[12, "oauth2-with-google"]], "OAuth2 with Facebook": [[12, "oauth2-with-facebook"]], "OAuth2 with Discord": [[12, "oauth2-with-discord"]], "Authorization using Tags": [[12, "authorization-using-tags"]], "Etiquetas e permiss\u00f5es": [[12, "tags-and-permissions"]], "Multiple Tags objects": [[12, "multiple-tags-objects"]], "Rede": [[13, "grid"]], "Key features": [[13, "key-features"]], "Basic grid example": [[13, "basic-grid-example"]], "The Grid object": [[13, "the-grid-object"]], "Searching and filtering": [[13, "searching-and-filtering"]], "CRUD settings": [[13, "crud-settings"]], "Custom columns": [[13, "custom-columns"]], "Usando templates": [[13, "using-templates"]], "Customizing style": [[13, "customizing-style"]], "A\u00e7\u00e3o personalizada Bot\u00f5es": [[13, "custom-action-buttons"]], "Bot\u00e3o Classe A\u00e7\u00e3o Amostra": [[13, "sample-action-button-class"]], "Using callable parameters": [[13, "using-callable-parameters"]], "Os campos de refer\u00eancia": [[13, "reference-fields"]], "De web2py para py4web": [[14, "from-web2py-to-py4web"]], "Simple conversion examples": [[14, "simple-conversion-examples"]], "\u201cHello world\u201d example": [[14, "hello-world-example"]], "\u201cRedirect with variables\u201d example": [[14, "redirect-with-variables-example"]], "\u201cReturning variables\u201d example": [[14, "returning-variables-example"]], "\u201cReturning args\u201d example": [[14, "returning-args-example"]], "\u201cReturn calling methods\u201d example": [[14, "return-calling-methods-example"]], "\u201cSetting up a counter\u201d example": [[14, "setting-up-a-counter-example"]], "\u201cView\u201d example": [[14, "view-example"]], "\u201cForm and flash\u201d example": [[14, "form-and-flash-example"]], "\u201cgrid\u201d example": [[14, "grid-example"]], "\u201cAccessing OS files\u201d example": [[14, "accessing-os-files-example"]], "\u201cauth\u201d example": [[14, "auth-example"]], "Advanced topics and examples": [[15, "advanced-topics-and-examples"]], "py4web and asyncio": [[15, "py4web-and-asyncio"]], "htmx": [[15, "htmx"]], "htmx usage in Form": [[15, "htmx-usage-in-form"]], "htmx usage in Grid": [[15, "htmx-usage-in-grid"]], "Autocomplete Widget using htmx": [[15, "autocomplete-widget-using-htmx"]], "utils.js": [[15, "utils-js"]], "string.format": [[15, "string-format"]], "The Q object": [[15, "the-q-object"]], "The T object": [[15, "the-t-object"]], "py4web: o manual de refer\u00eancia": [[16, "py4web-the-reference-manual"]], "Conte\u00fado:": [[16, null]], "\u00cdndices e tabelas": [[16, "indices-and-tables"]]}, "indexentries": {}}) \ No newline at end of file +Search.setIndex({"alltitles": {"A auto-refer\u00eancia e aliases": [[6, "self-reference-and-aliases"]], "A minimal form example without a database": [[12, "a-minimal-form-example-without-a-database"]], "A obten\u00e7\u00e3o de um `` row``": [[6, "fetching-a-row"]], "A p\u00e1gina Web principal": [[3, "the-main-web-page"]], "ANY_OF": [[12, "any-of"]], "Acknowledgments": [[0, "acknowledgments"]], "Adicionando atributos para campos e tabelas": [[6, "adding-attributes-to-fields-and-tables"]], "Advanced form design": [[12, "advanced-form-design"]], "Advanced topics and examples": [[16, null]], "Agrupamento e contando": [[6, "grouping-and-counting"]], "Ajuda, recursos e dicas": [[1, null]], "Aplicativos de modelo-less": [[6, "model-less-applications"]], "As fontes no GitHub": [[1, "the-sources-on-github"]], "Assinatura da DAL": [[6, "dal-signature"]], "Atalhos": [[6, "shortcuts"]], "Atualizar os arquivos de tradu\u00e7\u00e3o": [[11, "update-the-translation-files"]], "Authentication and authorization": [[13, null]], "Authentication using Auth": [[13, "authentication-using-auth"]], "Authorization using Tags": [[13, "authorization-using-tags"]], "Autocomplete Widget using htmx": [[16, "autocomplete-widget-using-htmx"]], "A\u00e7\u00e3o personalizada Bot\u00f5es": [[14, "custom-action-buttons"]], "Bancos de dados legados e tabelas com chave": [[6, "legacy-databases-and-keyed-tables"]], "Bancos de dados replicados": [[6, "replicated-databases"]], "Basic form example": [[12, "basic-form-example"]], "Basic grid example": [[14, "basic-grid-example"]], "Bot\u00e3o Classe A\u00e7\u00e3o Amostra": [[14, "sample-action-button-class"]], "Built-in helpers": [[10, "built-in-helpers"]], "CLEANUP": [[12, "cleanup"]], "CRUD settings": [[14, "crud-settings"]], "CRYPT": [[12, "crypt"]], "CSV (todas as tabelas ao mesmo tempo)": [[6, "csv-all-tables-at-once"]], "CSV (uma tabela de cada vez)": [[6, "csv-one-table-at-a-time"]], "CSV e sincroniza\u00e7\u00e3o de banco de dados remoto": [[6, "csv-and-remote-database-synchronization"]], "Caching e Memoize": [[5, "caching-and-memoize"]], "Campos computados": [[6, "computed-fields"]], "Campos virtuais": [[6, "virtual-fields"]], "Campos virtuais novo estilo (experimental)": [[6, "new-style-virtual-fields-experimental"]], "Campos virtuais velho antigo": [[6, "old-style-virtual-fields"]], "Caracter\u00edsticas avan\u00e7adas": [[6, "advanced-features"]], "Cascades no banco de dados": [[6, "database-cascades"]], "Caveats about fixtures": [[5, "caveats-about-fixtures"]], "Celery": [[16, "celery"]], "Checkbox validation": [[12, "checkbox-validation"]], "Client-side session in cookies": [[5, "client-side-session-in-cookies"]], "Combinando Rows": [[6, "combining-rows"]], "Como contribuir": [[1, "how-to-contribute"]], "Complexity and security validators": [[12, "complexity-and-security-validators"]], "Computed and Virtual fields": [[6, "computed-and-virtual-fields"]], "Configura\u00e7\u00f5es de quoting e case e do banco de dados": [[6, "database-quoting-and-case-settings"]], "Configura\u00e7\u00f5es padr\u00e3o de migra\u00e7\u00e3o": [[6, "default-migration-settings"]], "Construtor DAL": [[6, "dal-constructor"]], "Construtor Field": [[6, "field-constructor"]], "Construtor Table": [[6, "table-constructor"]], "Conte\u00fado:": [[17, null]], "Copiar dados de um para outro db": [[6, "copy-data-from-one-db-into-another"]], "Copying the _scaffold app": [[4, "copying-the-scaffold-app"]], "Creating an app": [[4, null]], "Custom columns": [[14, "custom-columns"]], "Custom forms": [[12, "custom-forms"]], "Custom widgets": [[12, "custom-widgets"]], "Customizing style": [[14, "customizing-style"]], "DAL introduction": [[6, "dal-introduction"]], "Database validators": [[12, "database-validators"]], "Date and time validators": [[12, "date-and-time-validators"]], "De web2py para py4web": [[15, null]], "Decoradores de conveni\u00eancia": [[5, "convenience-decorators"]], "Default page layout": [[8, "default-page-layout"]], "Deployment on Docker/Podman": [[2, "deployment-on-docker-podman"]], "Deployment on GCloud (aka GAE - Google App Engine)": [[2, "deployment-on-gcloud-aka-gae-google-app-engine"]], "Deployment on Ubuntu": [[2, "deployment-on-ubuntu"]], "Depura\u00e7\u00e3o py4web com PyCharm": [[1, "debugging-py4web-with-pycharm"]], "Depura\u00e7\u00e3o py4web com VScode": [[1, "debugging-py4web-with-vscode"]], "Dicas e sugest\u00f5es": [[1, "hints-and-tips"]], "Dictionaries and tuples with IS_IN_SET": [[12, "dictionaries-and-tuples-with-is-in-set"]], "Do princ\u00edpio": [[4, "from-scratch"]], "Domain-mapped apps": [[4, "domain-mapped-apps"]], "Em valores de retorno": [[4, "on-return-values"]], "Este manual": [[1, "this-manual"]], "Etiquetas e permiss\u00f5es": [[13, "tags-and-permissions"]], "Experimentar com o shell py4web": [[6, "experiment-with-the-py4web-shell"]], "Exportar e importar dados": [[6, "exporting-and-importing-data"]], "Express\u00f5es": [[6, "expressions"]], "Extending using variables": [[8, "extending-using-variables"]], "Falhas de conex\u00e3o (par\u00e2metro tentativas)": [[6, "connection-failures-attempts-parameter"]], "Fazendo uma conex\u00e3o segura": [[6, "making-a-secure-connection"]], "Field types and validators": [[6, "field-types-and-validators"]], "File upload field": [[12, "file-upload-field"]], "Fixa\u00e7\u00e3o migra\u00e7\u00f5es quebrados": [[6, "fixing-broken-migrations"]], "Fixtures personalizados": [[5, "custom-fixtures"]], "Fixures": [[5, null]], "Form structure manipulation": [[12, "form-structure-manipulation"]], "Foruml\u00e1rios": [[12, null]], "Generating raw SQL": [[6, "generating-raw-sql"]], "Google NoSQL (Datastore)": [[6, "google-nosql-datastore"]], "Google SQL": [[6, "google-sql"]], "Grids with checkboxes": [[14, "grids-with-checkboxes"]], "HTML e XML (uma tabela de cada vez)": [[6, "html-and-xml-one-table-at-a-time"]], "HTTPS": [[2, "https"]], "Helpers YATL": [[10, null]], "Helpers overview": [[10, "helpers-overview"]], "Helpers personalizados": [[10, "custom-helpers"]], "Heran\u00e7a de tabela": [[6, "table-inheritance"]], "IS_ALPHANUMERIC": [[12, "is-alphanumeric"]], "IS_DATE": [[12, "is-date"]], "IS_DATETIME": [[12, "is-datetime"]], "IS_DATETIME_IN_RANGE": [[12, "is-datetime-in-range"]], "IS_DATE_IN_RANGE": [[12, "is-date-in-range"]], "IS_DECIMAL_IN_RANGE": [[12, "is-decimal-in-range"]], "IS_EMAIL": [[12, "is-email"]], "IS_EMPTY_OR": [[12, "is-empty-or"]], "IS_EQUAL_TO": [[12, "is-equal-to"]], "IS_EXPR": [[12, "is-expr"]], "IS_FILE": [[12, "is-file"]], "IS_FLOAT_IN_RANGE": [[12, "is-float-in-range"]], "IS_IMAGE": [[12, "is-image"]], "IS_INT_IN_RANGE": [[12, "is-int-in-range"]], "IS_IN_DB": [[12, "is-in-db"]], "IS_IN_DB and Tagging": [[12, "is-in-db-and-tagging"]], "IS_IN_SET": [[12, "is-in-set"]], "IS_IN_SET and Tagging": [[12, "is-in-set-and-tagging"]], "IS_IPADDRESS": [[12, "is-ipaddress"]], "IS_IPV4": [[12, "is-ipv4"]], "IS_IPV6": [[12, "is-ipv6"]], "IS_JSON": [[12, "is-json"]], "IS_LENGTH": [[12, "is-length"]], "IS_LIST_OF": [[12, "is-list-of"]], "IS_LIST_OF_EMAILS": [[12, "is-list-of-emails"]], "IS_LOWER": [[12, "is-lower"]], "IS_MATCH": [[12, "is-match"]], "IS_NOT_EMPTY": [[12, "is-not-empty"]], "IS_NOT_IN_DB": [[12, "is-not-in-db"]], "IS_NULL_OR": [[12, "is-null-or"]], "IS_SAFE": [[12, "is-safe"]], "IS_SLUG": [[12, "is-slug"]], "IS_STRONG": [[12, "is-strong"]], "IS_TIME": [[12, "is-time"]], "IS_UPLOAD_FILENAME": [[12, "is-upload-filename"]], "IS_UPPER": [[12, "is-upper"]], "IS_URL": [[12, "is-url"]], "Implanta\u00e7\u00e3o em PythonAnywhere.com": [[2, "deployment-on-pythonanywhere-com"]], "Information workflow": [[8, "information-workflow"]], "Inner join": [[6, "inner-join"]], "Inserir e atualizar a partir de um dicion\u00e1rio": [[6, "inserting-and-updating-from-a-dictionary"]], "Instalando a partir de bin\u00e1rios": [[2, "installing-from-binaries"]], "Instalando a partir de fonte (localmente)": [[2, "installing-from-source-locally"]], "Instala\u00e7\u00e3o de fonte (globalmente)": [[2, "installing-from-source-globally"]], "Instala\u00e7\u00e3o e coloca\u00e7\u00e3o em funcionamento": [[2, null]], "Installing from pip, using a virtual environment": [[2, "installing-from-pip-using-a-virtual-environment"]], "Installing from pip, without virtual environment": [[2, "installing-from-pip-without-virtual-environment"]], "Interface de autentica\u00e7\u00e3o": [[13, "auth-ui"]], "Internacionaliza\u00e7\u00e3o": [[11, null]], "Joins and Relations": [[6, "joins-and-relations"]], "Key features": [[14, "key-features"]], "LDAP": [[13, "ldap"]], "Left outer join": [[6, "left-outer-join"]], "Linguagem de template YATL": [[8, null]], "Local de pasta do banco de dados": [[6, "database-folder-location"]], "MSSQL (Microsoft SQL Server)": [[6, "mssql-microsoft-sql-server"]], "Mais sobre envios": [[6, "more-on-uploads"]], "Many to many relation": [[6, "many-to-many-relation"]], "Marca\u00e7\u00e3o de registros": [[6, "tagging-records"]], "Melhoramento": [[2, "upgrading"]], "Migra\u00e7\u00e3o resumo controle": [[6, "migration-control-summary"]], "Migra\u00e7\u00f5es": [[6, "migrations"]], "Mobile development": [[8, "mobile-development"]], "Modelos": [[4, "templates"]], "Multiple Tags objects": [[13, "multiple-tags-objects"]], "Multiple fixtures": [[5, "multiple-fixtures"]], "MySQL": [[6, "mysql"]], "Nota sobre novo DAL e adaptadores": [[6, "note-on-new-dal-and-adapters"]], "O Dashboard": [[3, null]], "O fixture DAL": [[5, "the-dal-fixture"]], "O fixture flash": [[5, "the-flash-fixture"]], "O grupo Google": [[1, "the-google-group"]], "O objeto `` request``": [[4, "the-request-object"]], "O pool de conex\u00f5es": [[6, "connection-pooling"]], "O que \u00e9 py4web?": [[0, null]], "OAuth2 with Discord": [[13, "oauth2-with-discord"]], "OAuth2 with Facebook": [[13, "oauth2-with-facebook"]], "OAuth2 with Google": [[13, "oauth2-with-google"]], "Operadores l\u00f3gicos": [[6, "logical-operators"]], "Op\u00e7\u00e3o `` comando call``": [[2, "call-command-option"]], "Op\u00e7\u00e3o `` comando new_app``": [[2, "new-app-command-option"]], "Op\u00e7\u00e3o `` comando run``": [[2, "run-command-option"]], "Op\u00e7\u00e3o `` comando set_password``": [[2, "set-password-command-option"]], "Op\u00e7\u00e3o `` comando setup``": [[2, "setup-command-option"]], "Op\u00e7\u00e3o `` comando shell``": [[2, "shell-command-option"]], "Op\u00e7\u00e3o `` comando version``": [[2, "version-command-option"]], "Op\u00e7\u00f5es de linha de comando": [[2, "command-line-options"]], "Or\u00e1culo": [[6, "oracle"]], "Os campos de refer\u00eancia": [[14, "reference-fields"]], "Os valores por defeito com `` `` coalesce`` e coalesce_zero``": [[6, "default-values-with-coalesce-and-coalesce-zero"]], "Other validators": [[12, "other-validators"]], "Outros operadores": [[6, "other-operators"]], "Outros par\u00e2metros do construtor DAL": [[6, "other-dal-constructor-parameters"]], "PAM": [[13, "pam"]], "Page layout standard structure": [[8, "page-layout-standard-structure"]], "Palavras-chave reservadas": [[6, "reserved-keywords"]], "Pegadinhas": [[6, "gotchas"]], "Personalizados `` tipos Field``": [[6, "custom-field-types"]], "Plataformas e pr\u00e9-requisitos suportados": [[2, "supported-platforms-and-prerequisites"]], "Plugins de Autentica\u00e7\u00e3o": [[13, "auth-plugins"]], "Pluralizar": [[11, "pluralize"]], "Primeira corrida": [[2, "first-run"]], "Procedimentos de configura\u00e7\u00e3o": [[2, "setup-procedures"]], "Pr\u00e9-requisitos": [[1, "prerequisites"]], "P\u00e1ginas est\u00e1ticas": [[4, "static-web-pages"]], "P\u00e1ginas web din\u00e2micas": [[4, "dynamic-web-pages"]], "Range, set and equality validators": [[12, "range-set-and-equality-validators"]], "Raw SQL": [[6, "raw-sql"]], "Recursivas `` s SELECT``": [[6, "recursive-selects"]], "Recursos": [[1, "resources"]], "Rede": [[14, null]], "Renderizando Rows com represent": [[6, "rendering-rows-using-represent"]], "Representa\u00e7\u00e3o de dados": [[6, "data-representation"]], "RestAPI GET": [[7, "restapi-get"]], "RestAPI policies and actions": [[7, "restapi-policies-and-actions"]], "RestAPI practical examples": [[7, "restapi-practical-examples"]], "Rotas": [[4, "routes"]], "SQLite": [[6, "sqlite"]], "Searching and filtering": [[14, "searching-and-filtering"]], "Selects com cache": [[6, "caching-selects"]], "Sending messages using a background task": [[16, "sending-messages-using-a-background-task"]], "Server-side DOM": [[10, "server-side-dom"]], "Server-side session anywhere": [[5, "server-side-session-anywhere"]], "Server-side session in Redis": [[5, "server-side-session-in-redis"]], "Server-side session in database": [[5, "server-side-session-in-database"]], "Server-side session in memcache": [[5, "server-side-session-in-memcache"]], "Sess\u00e3o no Dashboard": [[3, "login-into-the-dashboard"]], "Sharing sessions": [[5, "sharing-sessions"]], "Simple conversion examples": [[15, "simple-conversion-examples"]], "Sintaxe b\u00e1sica": [[8, "basic-syntax"]], "Sorted options": [[12, "sorted-options"]], "Special installations": [[2, "special-installations"]], "Special type validators": [[12, "special-type-validators"]], "Standard widgets": [[12, "standard-widgets"]], "Strings de conex\u00e3o (o par\u00e2metro uri)": [[6, "connection-strings-the-uri-parameter"]], "Substrings": [[6, "substrings"]], "Supported databases": [[6, "supported-databases"]], "Tabelas pregui\u00e7osos": [[6, "lazy-tables"]], "Table methods": [[6, "table-methods"]], "Template Functions": [[8, "template-functions"]], "Temporiza\u00e7\u00e3o de consultas": [[6, "timing-queries"]], "Text format validators": [[12, "text-format-validators"]], "The Auth fixture": [[5, "the-auth-fixture"]], "The Condition fixture": [[5, "the-condition-fixture"]], "The DAL: a quick tour": [[6, "the-dal-a-quick-tour"]], "The Database Abstraction Layer (DAL)": [[6, null]], "The Discord server": [[1, "the-discord-server"]], "The Form constructor": [[12, "the-form-constructor"]], "The Grid object": [[14, "the-grid-object"]], "The Inject fixture": [[5, "the-inject-fixture"]], "The Q object": [[16, "the-q-object"]], "The RestAPI": [[7, null]], "The RestAPI response": [[7, "the-restapi-response"]], "The Session fixture": [[5, "the-session-fixture"]], "The T object": [[16, "the-t-object"]], "The Template fixture": [[5, "the-template-fixture"]], "The Translator fixture": [[5, "the-translator-fixture"]], "The URLsigner fixture": [[5, "the-urlsigner-fixture"]], "The _scaffold app": [[4, "the-scaffold-app"]], "The scheduler": [[16, "the-scheduler"]], "The sidecar parameter": [[12, "the-sidecar-parameter"]], "Transa\u00e7\u00e3o distribu\u00edda": [[6, "distributed-transaction"]], "Tutoriais e v\u00eddeo": [[1, "tutorials-and-video"]], "Two Factor Authentication": [[13, "two-factor-authentication"]], "Um local de trabalho python moderna": [[1, "a-modern-python-workplace"]], "Um para muitos rela\u00e7\u00e3o": [[6, "one-to-many-relation"]], "Understanding the design": [[2, "understanding-the-design"]], "Usando DAL sem definir tabelas": [[6, "using-dal-without-define-tables"]], "Usando o DAL \u201cstand-alone\u201d": [[6, "using-the-dal-stand-alone"]], "Usando templates": [[14, "using-templates"]], "Usando um seleto para uso de mem\u00f3ria inferior \u00e0 base de iterador": [[6, "using-an-iterator-based-select-for-lower-memory-use"]], "User Impersonation": [[13, "user-impersonation"]], "Using Auth inside actions": [[13, "using-auth-inside-actions"]], "Using Fixtures": [[5, "using-fixtures"]], "Using Inject": [[10, "using-inject"]], "Using callable parameters": [[14, "using-callable-parameters"]], "Using the dashboard app with databases": [[6, "using-the-dashboard-app-with-databases"]], "Validation functions": [[12, "validation-functions"]], "Valida\u00e7\u00e3o de formul\u00e1rio": [[12, "form-validation"]], "WSGI": [[2, "wsgi"]], "Watch for files change": [[4, "watch-for-files-change"]], "Widgets": [[12, "widgets"]], "`` A``": [[10, "a"]], "`` BEAUTIFY``": [[10, "beautify"]], "`` BODY``": [[10, "body"]], "`` Belongs``": [[6, "belongs"]], "`` CAT``": [[10, "cat"]], "`` Comando SELECT``": [[6, "select-command"]], "`` Count``, `` isempty``, `` DELETE``, `` update``": [[6, "count-isempty-delete-update"]], "`` Def \u2026 return``": [[8, "def-return"]], "`` Div``": [[10, "div"]], "`` Drop``": [[6, "drop"]], "`` EM``": [[10, "em"]], "`` Find``, `` exclude``, `` sort``": [[6, "find-exclude-sort"]], "`` Form``": [[10, "form"]], "`` Format``: representa\u00e7\u00e3o da ficha": [[6, "format-record-representation"]], "`` H1``, `` h2``, `` H3``, `` H4``, `` H5``, `` H6``": [[10, "h1-h2-h3-h4-h5-h6"]], "`` HEAD``": [[10, "head"]], "`` HTML``": [[10, "html"]], "`` IMG``": [[10, "img"]], "`` INPUT``": [[10, "input"]], "`` I``": [[10, "i"]], "`` Id``: Notas sobre a chave prim\u00e1ria": [[6, "id-notes-about-the-primary-key"]], "`` If \u2026 elif \u2026 else``": [[8, "if-elif-else"]], "`` Insert``": [[6, "insert"]], "`` LI``": [[10, "li"]], "`` Label``": [[10, "label"]], "`` Like``, `` ilike``, `` regexp``, `` startswith``, `` endswith``, `` contains``, `` upper``, `` lower``": [[6, "like-ilike-regexp-startswith-endswith-contains-upper-lower"]], "`` Lista: `` e `` contains``": [[6, "list-type-and-contains"]], "`` Migrate``, `` fake_migrate``": [[6, "migrate-fake-migrate"]], "`` OL``": [[10, "ol"]], "`` OPTION``": [[10, "option"]], "`` On_define``": [[6, "on-define"]], "`` Orderby``, `` groupby``, `` limitby``, `` distinct``, `` having``, `` orderby_on_limitby``, `` join``, `` left``, `` cache``": [[6, "orderby-groupby-limitby-distinct-having-orderby-on-limitby-join-left-cache"]], "`` PRE``": [[10, "pre"]], "`` P``": [[10, "p"]], "`` Para \u2026 in``": [[8, "for-in"]], "`` Primarykey``: Suporte para tabelas legadas": [[6, "primarykey-support-for-legacy-tables"]], "`` Query``, `` Set``, `` Rows``": [[6, "query-set-rows"]], "`` Redefine``": [[6, "redefine"]], "`` Rname``: nome real": [[6, "rname-real-name"]], "`` SCRIPT``": [[10, "script"]], "`` SELECT``": [[10, "select"]], "`` SPAN``": [[10, "span"]], "`` STYLE``": [[10, "style"]], "`` Sequence_name``": [[6, "sequence-name"]], "`` Sum``, `` avg``, `` min``, `` `` max`` e len``": [[6, "sum-avg-min-max-and-len"]], "`` TABLE``, `` TR``, `` TD``": [[10, "table-tr-td"]], "`` TAG``": [[10, "tag"]], "`` TBODY``": [[10, "tbody"]], "`` TEXTAREA``": [[10, "textarea"]], "`` THEAD``": [[10, "thead"]], "`` TH``": [[10, "th"]], "`` TITLE``": [[10, "title"]], "`` TT``": [[10, "tt"]], "`` Table_class``": [[6, "table-class"]], "`` Tentar \u2026 exceto \u2026 else \u2026 finally``": [[8, "try-except-else-finally"]], "`` Trigger_name``": [[6, "trigger-name"]], "`` UL``": [[10, "ul"]], "`` URL``": [[10, "url"]], "`` Update_or_insert``": [[6, "update-or-insert"]], "`` Update_record``": [[6, "update-record"]], "`` Validate_and_insert``, `` validate_and_update``": [[6, "validate-and-insert-validate-and-update"]], "`` While``": [[8, "while"]], "`` XML``": [[10, "xml"]], "`` Year``, `` month``, `` day``, `` hour``, `` minutes``, `` seconds``": [[6, "year-month-day-hour-minutes-seconds"]], "`` _Lastsql``": [[6, "lastsql"]], "`` `` As_dict`` e as_list``": [[6, "as-dict-and-as-list"]], "`` `` Filter_in`` e filter_out``": [[6, "filter-in-and-filter-out"]], "`` `` First`` e last``": [[6, "first-and-last"]], "`` `` Plural`` e singular``": [[6, "plural-and-singular"]], "`` `` commit`` e rollback``": [[6, "commit-and-rollback"]], "`` case``": [[6, "case"]], "`` executesql``": [[6, "executesql"]], "`` polymodel``": [[6, "polymodel"]], "assinatura define_table": [[6, "define-table-signature"]], "block and super": [[8, "block-and-super"]], "cache, em cache": [[6, "cache-cacheable"]], "children": [[10, "children"]], "distinto": [[6, "distinct"]], "extend and include": [[8, "extend-and-include"]], "filtros comuns": [[6, "common-filters"]], "find": [[10, "find"]], "groupby, tendo": [[6, "groupby-having"]], "htmx": [[16, "htmx"]], "htmx usage in Form": [[16, "htmx-usage-in-form"]], "htmx usage in Grid": [[16, "htmx-usage-in-grid"]], "juntar-se, deixou": [[6, "join-left"]], "limitby": [[6, "limitby"]], "modifica\u00e7\u00e3o da tabela e campo em tempo de execu\u00e7\u00e3o": [[6, "run-time-field-and-table-modification"]], "ordenar por": [[6, "orderby"]], "orderby_on_limitby": [[6, "orderby-on-limitby"]], "py4web and asyncio": [[16, "py4web-and-asyncio"]], "py4web model": [[6, "py4web-model"]], "py4web: o manual de refer\u00eancia": [[17, null]], "retornos de chamada no registro de inser\u00e7\u00e3o, exclus\u00e3o e atualiza\u00e7\u00e3o": [[6, "callbacks-on-record-insert-delete-and-update"]], "string.format": [[16, "string-format"]], "two_factor_required": [[13, "two-factor-required"]], "two_factor_send": [[13, "two-factor-send"]], "two_factor_tries": [[13, "two-factor-tries"]], "two_factor_validate": [[13, "two-factor-validate"]], "utils.js": [[16, "utils-js"]], "versionamento recorde": [[6, "record-versioning"]], "\u00cdndices": [[6, "indexes"]], "\u00cdndices e tabelas": [[17, "indices-and-tables"]], "\u201cAccessing OS files\u201d example": [[15, "accessing-os-files-example"]], "\u201cForm and flash\u201d example": [[15, "form-and-flash-example"]], "\u201cHello world\u201d example": [[15, "hello-world-example"]], "\u201cRedirect with variables\u201d example": [[15, "redirect-with-variables-example"]], "\u201cReturn calling methods\u201d example": [[15, "return-calling-methods-example"]], "\u201cReturning args\u201d example": [[15, "returning-args-example"]], "\u201cReturning variables\u201d example": [[15, "returning-variables-example"]], "\u201cSetting up a counter\u201d example": [[15, "setting-up-a-counter-example"]], "\u201cView\u201d example": [[15, "view-example"]], "\u201cauth\u201d example": [[15, "auth-example"]], "\u201cgrid\u201d example": [[15, "grid-example"]]}, "docnames": ["chapter-01", "chapter-02", "chapter-03", "chapter-04", "chapter-05", "chapter-06", "chapter-07", "chapter-08", "chapter-09", "chapter-1", "chapter-10", "chapter-11", "chapter-12", "chapter-13", "chapter-14", "chapter-15", "chapter-16", "index"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["chapter-01.rst", "chapter-02.rst", "chapter-03.rst", "chapter-04.rst", "chapter-05.rst", "chapter-06.rst", "chapter-07.rst", "chapter-08.rst", "chapter-09.rst", "chapter-1.rst", "chapter-10.rst", "chapter-11.rst", "chapter-12.rst", "chapter-13.rst", "chapter-14.rst", "chapter-15.rst", "chapter-16.rst", "index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"0": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "00": 7, "01": 7, "02": [6, 12], "03": [2, 6, 7], "04": 2, "04t07": 7, "05": 7, "08": 12, "0x4e86": 12, "0x7fa533ff7640": 10, "1": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 14, 15, 16], "10": [2, 6, 7, 8, 10, 11, 12, 13, 16], "100": [2, 6, 7, 8, 12, 16], "1000": [5, 6, 12, 16], "1000ms": 16, "100px": 16, "1024": 12, "1048576": 12, "10px": 14, "11": [6, 12], "111111": 16, "11211": 5, "12": 12, "120": 6, "123": [10, 12], "123218": 7, "123456": 12, "125": 6, "127": [2, 3, 4, 5, 12, 14], "13": [6, 12], "132635": 7, "14": [8, 12, 14], "15": [6, 12, 13, 14], "16": 12, "168": 12, "169": 12, "16px": 8, "172": [6, 12], "174": 6, "178974": 7, "19": [6, 7, 12], "192": [6, 12], "1963": 12, "198": 6, "199": 12, "19t05": 7, "1e100": 6, "1kb": 12, "1l": 6, "1mb": 12, "1pkogiy59xj8co8": 8, "2": [5, 6, 7, 8, 10, 11, 12, 13, 15, 16], "20": [2, 7, 11, 12], "200": [7, 12, 15, 16], "2001": 12, "2002": 12, "2005": 6, "2007": 0, "2008": 12, "2009": 12, "200ms": 16, "200x200": 12, "2010": 6, "2012": 6, "2013": 6, "2015": 0, "2018": 6, "2019": 7, "201988": 7, "2020": 1, "20201112": 3, "2021": 7, "2022": 5, "207": 6, "217": 6, "22": 13, "227": 6, "239": 6, "2396": 12, "24": 12, "254": 12, "255": 12, "256": 6, "2616": 12, "28": 12, "2em": 16, "2px": 16, "3": [0, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "30": [2, 6, 8, 12], "301": 15, "309903": 7, "31": [6, 7, 12], "32": [6, 12], "322494": 7, "33": [6, 12], "34": 7, "3490": 12, "3492": 12, "35": 6, "355181": 7, "3600": [5, 6], "366288": 7, "38": 7, "3em": 16, "4": [2, 5, 6, 7, 8, 12, 14], "40": [2, 10, 16], "400": [5, 15], "401": 13, "404": [5, 13], "405515": 7, "43": 6, "45": [8, 12], "451907": 7, "453020": 7, "456": 12, "466030": 7, "4e": 12, "5": [5, 6, 7, 8, 11, 12, 13, 14, 16], "50": [2, 7], "500": 16, "500ms": 16, "512": 6, "53": 12, "54": 8, "559918": 7, "58": 8, "59": 12, "6": [5, 6, 7, 12, 13, 15, 16], "60": [5, 6], "63": [6, 12], "6379": 5, "64": [6, 8, 16], "65": 6, "6to4": 12, "7": [2, 7, 8, 12, 15], "70": 7, "74": 6, "75": 7, "768": 6, "8": [2, 6, 7, 12], "80": [4, 7, 12], "8000": [2, 3, 4, 12, 14], "86": 12, "8601": 7, "8em": [8, 16], "9": [6, 7, 12, 14, 15], "90": [6, 7], "91": 6, "95": 6, "97": 6, "974953": 7, "99": 6, "A": [1, 2, 4, 5, 7, 8, 11, 13, 14, 15, 16, 17], "AS": 6, "Ao": [4, 6, 8, 14], "As": [3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16], "COMO": 6, "Com": 6, "Comando": 17, "Como": [6, 17], "Da": 6, "De": [0, 1, 4, 6, 17], "Do": [6, 8, 12, 14, 17], "E": [1, 6, 13], "Ela": [1, 6, 8], "Ele": [2, 3, 5, 6, 10], "Eles": [0, 4, 6], "Em": [2, 5, 6, 8, 10], "Essas": 0, "Esse": 4, "Esses": [5, 6], "Esta": [0, 1, 2, 4, 6], "Estas": 10, "Este": [2, 6, 8, 10, 14, 15], "Estes": 6, "For": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "Fora": 0, "H\u00e1": [1, 5, 6, 14], "Isso": [2, 4, 6], "Isto": [0, 2, 3, 4, 5, 6, 10, 14], "Mas": [1, 4, 6], "NO": 6, "Na": 0, "Nem": 6, "No": [2, 5, 6, 8, 14], "Nos": 6, "N\u00f3s": [0, 1, 5], "O": [2, 8, 10, 12, 13, 17], "OU": 14, "Os": [0, 3, 4, 8, 10, 13, 17], "Por": [2, 3, 4, 5, 6, 10], "SE": 6, "Se": [1, 2, 3, 4, 6, 8, 10, 14], "Seu": 6, "Tamb\u00e9m": 6, "Um": 5, "Uma": [4, 6, 8, 10], "_": [2, 5, 12, 16], "__": [1, 2, 4], "__dict__": 5, "__file__": [5, 7, 14], "__init": 2, "__init__": [2, 4, 5, 6, 7, 13, 14, 15, 16], "__prerequisite__": 5, "__prerequisites__": [5, 13], "__str__": [5, 8, 10], "_action": [10, 14], "_adapt": 6, "_after_delet": 6, "_after_insert": 6, "_after_updat": 6, "_alt": 10, "_always_": 13, "_and": 12, "_antes_": 6, "_autocomplet": 16, "_autocomplete_search_fields": 16, "_before_delet": 6, "_before_insert": 6, "_before_updat": 6, "_bgcolor": 10, "_c": 10, "_checked": 10, "_class": [5, 10, 12, 16], "_cols": 10, "_common_filt": 6, "_count": 6, "_dashboard": [2, 3], "_dat": 10, "_db": 6, "_dbnam": 6, "_default": [2, 4], "_delet": 6, "_disabled": 10, "_documentation": [1, 3], "_enable_record_versioning": 6, "_extr": 6, "_format": 6, "_href": [5, 8, 10, 12, 14], "_hx": 16, "_id": [6, 10, 12, 16], "_insert": 6, "_listify": 6, "_method": [10, 14], "_nam": [6, 10, 12, 14, 16], "_next_url": 13, "_nonreserved": 6, "_onclick": 12, "_placehold": [12, 16], "_rows": 10, "_scaffold": [5, 8, 10, 12, 13, 14, 15, 17], "_search": 16, "_select": 6, "_selected": 10, "_sesson": 5, "_src": 10, "_style": [12, 16], "_tabl": [6, 12, 16], "_tablenam": 6, "_tag_": 13, "_timings": 6, "_titl": [12, 16], "_type": [6, 10, 12, 14, 16], "_u": 10, "_updat": 6, "_ur": 6, "_valu": [10, 12, 14, 16], "_xmlns": 10, "aaabaaeaaqeaaaeaiaawaaaafgaaacgaaaabaaaaagaaaaeaiaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaapaaaaa": 8, "ab": [10, 12], "aba": 3, "abaix": 6, "abas": 3, "abbreviated": 12, "abc": [10, 12], "abert": [1, 3, 6], "ability": 15, "able": [5, 6, 12], "abort": [4, 6], "about": [6, 7, 13, 14, 15, 16, 17], "abov": [4, 5, 6, 7, 8, 12, 14, 16], "abracadabr": 2, "abrir": 2, "absent": 2, "absolut": [2, 6, 15], "abspath": 4, "abstraction": [5, 15, 17], "abstra\u00e7\u00e3": 6, "acab": 6, "accdesc": 6, "accept": [4, 5, 12, 16], "acceptanc": 12, "accepted": [1, 2, 6, 12, 13, 15, 16], "accepting": 10, "accepts": 12, "access": [2, 4, 5, 6, 10, 13, 15, 16], "accessed": [6, 10, 12], "accessibl": 4, "accessing": [5, 6], "accnum": 6, "accomplish": [5, 6, 13], "accomplished": [6, 13, 15], "according": 8, "accordingly": 8, "account": 6, "acctype": 6, "aceit": [6, 11, 12], "aceler": 6, "acert": 2, "acess": [0, 2, 4, 5, 6], "acess\u00f3ri": 4, "achieved": [5, 15], "acim": [6, 7, 8, 13], "acion": 6, "acknowledgments": 17, "acompanh": 6, "acontec": 6, "acord": 6, "acrescent": 6, "across": [6, 8], "act": 6, "action": [4, 5, 6, 7, 10, 12, 13, 14, 15, 16], "action_button": 14, "action_token": [5, 13], "actions": [4, 5, 6, 14, 15, 17], "activ": [6, 13], "activat": [2, 13], "activated": 13, "activating": 2, "activiti": 6, "acts": 12, "actu": 6, "actual": [5, 6, 8, 13, 14], "actualiz": 6, "actually": [6, 8], "ac\u00e7\u00e3": [4, 5, 6, 7], "ad": 13, "adapt": 13, "adapter_args": 6, "adapters": [6, 16], "add": [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "added": [0, 2, 5, 12, 13, 16], "adding": [6, 12, 14], "addition": [6, 8], "additional": [4, 5, 6, 7, 8, 12, 14], "additional_cl": 14, "additional_styl": 14, "address": [2, 12, 13], "adds": 16, "adi": 6, "adiant": 13, "adicion": [2, 5, 11, 12, 13, 14], "adicional": [2, 5, 6], "adi\u00e7\u00e3": 6, "admin": 15, "administr": [0, 6], "administrativ": 2, "administrator": 2, "adquir": 6, "advanc": [2, 13, 14], "advanced": [14, 17], "advantag": [2, 6, 8, 16], "advisabl": 6, "advised": 14, "affecting": 6, "after": [5, 6, 8, 12, 13, 14, 16], "after_connection": 6, "after_delet": 6, "after_insert": 6, "after_updat": 6, "aftermath": 6, "again": [2, 4, 5, 6, 12, 13, 16], "against": [6, 7, 10, 12, 13, 14], "age": 6, "aggregat": 6, "agir": [6, 10], "agn\u00f3st": 4, "agor": [2, 4, 5, 6, 8], "agrad": 10, "agreg": 6, "aid": 6, "aims": 0, "aind": [2, 6, 13], "ajax": [8, 14, 16], "ajud": [0, 2, 4, 6, 15, 17], "ajust": 6, "aka": 1, "alcanc": 6, "alchemy": 6, "aleat\u00f3r": 6, "alert": [5, 8, 10, 12, 15], "alerts": [5, 8, 15], "alex": 6, "alfar": [0, 1], "alg": 12, "algo": [5, 6, 7, 8], "algorithm": [5, 12], "algum": [4, 6, 10], "alguns": [0, 2, 4, 5, 6, 7, 8, 10], "ali": [6, 12], "aliment": 6, "aliv": 16, "all": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "allocated": 12, "allow": [6, 7, 8, 10, 12, 13, 14, 16], "allowed": [6, 7, 12, 13], "allowed_actions": 13, "allowed_attribut": 10, "allowed_overrid": 12, "allowed_patterns": 7, "allowed_schem": 12, "allowing": [1, 12, 16], "allows": [2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 16], "almost": 5, "along": [2, 4, 10, 12, 16], "alphabetically": 12, "alphanumeric": 12, "already": [2, 4, 5, 10, 12, 13, 14], "also": [0, 1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "alt": 10, "alter": [0, 2, 4, 5, 6, 11], "altering": 6, "altern": 6, "alternat": [5, 16], "alternativ": [2, 6, 8, 12], "although": [2, 8, 12], "alvo": 6, "always": [0, 2, 4, 5, 6, 12, 16], "al\u00e9m": [4, 6, 14], "am": [12, 16], "amazon": 6, "ambas": 6, "ambient": 2, "ambigu": [4, 6], "ambos": [4, 5, 6], "among": [6, 12], "amount": 6, "ampli": 1, "an": [0, 1, 2, 3, 5, 6, 8, 10, 12, 13, 14, 15, 16, 17], "analis": [4, 6, 11], "analogy": 15, "ancestor": 10, "anchor": 16, "and": [0, 1, 2, 3, 4, 5, 10, 17], "andaim": 4, "andrew": 1, "anex": 6, "angle": 8, "angul": 16, "angularjs": 0, "aninh": [6, 10], "anonymous": [6, 8, 12], "anoth": [4, 5, 6, 8, 12, 13], "anotherpath": 5, "ansi": 6, "answer": 1, "anteced": 4, "anterior": [2, 6], "antes": [1, 6, 8, 10, 13, 14], "antig": 15, "any": [2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "anyhow": 2, "anyobj": 6, "anything": 4, "anyway": 6, "anywher": [8, 15], "an\u00e1lis": 6, "apag": 12, "aparec": 6, "aparent": 6, "apen": [2, 5, 6, 7], "api": [0, 6, 7, 10, 12, 13, 16], "api_key": 16, "api_version": 7, "apis": [0, 6, 13, 15], "aplic": [0, 2, 3, 4, 5, 14, 15], "apoi": 6, "apont": 6, "app": [0, 1, 3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17], "app1": 5, "app1_session": 5, "app2": 5, "app_fold": 15, "app_nam": [2, 4, 5, 6], "app_watch_handl": 4, "appadmin": [0, 6], "appe": [6, 12], "append": [6, 10, 12, 14, 16], "append_id": 14, "appended": [5, 6], "apple": 12, "appli": [6, 12, 16], "application": [2, 4, 5, 6, 8, 12, 13, 15, 16], "applications": [0, 2, 3, 4, 6, 12, 13, 14], "applied": [5, 6, 12, 14, 16], "apply": [0, 5, 12, 13, 14], "applying": [6, 14], "appnam": [2, 4, 5, 13, 15], "appname_session": 5, "approach": [6, 15], "appropriat": [6, 12], "appropriately": 6, "approv": 13, "apps": [0, 1, 2, 3, 5, 6, 8, 13, 15, 17], "apps_fold": 2, "aprend": 1, "apresent": 8, "apropri": 6, "aproxim": 6, "apt": 13, "ap\u00f3s": [2, 6, 14], "aqu": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14], "aquel": [6, 13], "arbitrary": [4, 13], "arbitr\u00e1ri": 6, "archive_db": 6, "archive_nam": 6, "are": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "aren": 6, "args": [1, 2, 6], "argument": [2, 4, 5, 6, 8, 10, 12, 13, 16], "arguments": [2, 5, 6, 8, 10, 12, 13, 15, 16], "arithmetic": 12, "armazen": [0, 2, 5, 6, 10], "around": [1, 15], "arquiv": [0, 2, 3, 4, 5, 6, 13, 17], "arrang": 12, "array": 16, "arriv": [0, 6], "as_ordered_dict": 6, "asci": 12, "asid": 16, "ask": 16, "asked": [2, 13], "asking": 5, "aspas": 6, "aspects": 4, "assert": [6, 16], "assets": 2, "assigned": [6, 12, 13, 15], "assigning": [15, 16], "assignment": 8, "assigns": 16, "assim": [6, 8], "assinatur": [4, 5], "assist": 4, "assistent": 10, "assoc": [6, 11], "associated": [6, 12, 13], "assum": [2, 5, 6, 7, 11, 12], "assumed": 15, "assuming": [5, 6], "async": 16, "asynchronously": 16, "asynci": 17, "at": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "atend": [2, 6], "ativ": [2, 6], "atrav\u00e9s": [6, 8, 10, 13], "atribut": [5, 10], "atribu\u00edd": 6, "attached": [6, 13], "attaching": 15, "attacks": [5, 10], "attempted": 12, "attempting": 6, "attempts": [6, 13], "attention": [4, 6], "attribut": [4, 5, 6, 10, 12, 14, 15, 16], "attributes_plugin": 16, "attributespluginhtmx": 16, "attrs": [12, 14, 16], "atual": [2, 6], "atualiz": [2, 17], "aug": 12, "august": 12, "aul": 14, "aut": 10, "autentic": 6, "auth": [0, 2, 4, 6, 8, 10, 16, 17], "auth_group": 13, "auth_groups": 13, "auth_plugins": 13, "auth_us": [5, 6, 13, 16], "auth_user_tag_groups": [6, 13], "auth_user_tagged_groups": 13, "authenticat": 13, "authenticated": [5, 6], "authentication": [5, 14, 17], "authoriz": [6, 7], "authorization": [5, 6, 17], "authorized": 13, "auto_import": 6, "auto_process": [14, 16], "autocomplete_query": 16, "autodelet": 6, "autogenerated": 6, "automat": [2, 4, 6, 8, 10, 13, 14], "automatic": [4, 6, 8, 15], "automatically": [2, 3, 4, 5, 6, 12, 13, 14, 15, 16], "autoriz": 6, "auxili": [5, 6, 8, 10], "availabl": [1, 2, 6, 8, 10, 13, 15, 16], "avali": [6, 8], "avanc": 17, "avis": [5, 6, 13], "avoid": [1, 2, 5, 6, 12], "avoided": 8, "avoiding": 5, "avoids": 6, "awar": 6, "awesom": [8, 14], "axel": 0, "axolotl": 0, "azul": 4, "a\u00e7\u00e3": [4, 5, 6, 13, 17], "a\u00e7\u00f5": 5, "a\u00e7\u00fac": 5, "b": [5, 6, 8, 10, 12, 15, 16], "back": [5, 6, 12], "backend": [4, 6], "backends": 6, "background": [2, 4, 12, 17], "backported": 6, "backslash": 4, "backup": [2, 6, 12], "backward": 12, "backwards": [0, 12], "bad_days": 6, "bails": 6, "banan": 12, "banc": [0, 1, 3, 4, 5, 13], "bar": [8, 14], "barc": 6, "barr": 4, "barri": 0, "bas": [0, 2, 4, 8, 14, 16], "base64": [6, 8, 16], "base_dn": 13, "baseadapt": 6, "based": [0, 2, 3, 4, 5, 7, 8, 12, 13, 14, 15, 16], "bash": 2, "basic": [2, 5, 17], "basically": 16, "bast": 2, "bat": 2, "batman": [7, 12, 14], "battl": 0, "be": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "beasley": 0, "becaus": [0, 2, 4, 5, 6, 8, 10, 12, 13, 14, 16], "becom": [12, 16], "bed": 11, "been": [0, 2, 5, 6, 8, 12, 13], "befor": [2, 5, 6, 8, 12, 13, 14], "before_delet": 6, "before_insert": 6, "before_updat": 6, "begin": [12, 14], "beginners": 2, "beginning": [8, 12, 13], "behavior": [5, 6, 12, 15, 16], "behaviour": [2, 6, 14], "being": [4, 5, 6, 7, 8, 12, 16], "believ": [0, 5], "belong": [13, 15], "belonging": 6, "belongs": 13, "below": [5, 6, 7, 12, 14], "bem": [0, 4, 5, 6, 13], "benefits": 16, "benef\u00edci": 6, "best": [2, 5, 6], "bett": [0, 1, 2, 4, 6, 14, 15, 16], "between": [5, 10, 12, 15], "beyond": 12, "bgcolor": 10, "bibliotec": [0, 4, 11], "big": 15, "bigint": 6, "bigint_id": 6, "bilhet": [0, 3, 6], "bilh\u00e9t": 0, "bin": [1, 2], "binari": 2, "binary": 6, "bind": 16, "bin\u00e1ri": 6, "birthplac": 6, "bit": 12, "bitbucket": 1, "black": [8, 12], "blank": [12, 14], "blanks": 12, "blink": 16, "blob": [1, 6], "bloc": [8, 10], "block": [4, 13, 16], "blockquot": 10, "blocks": 8, "blog": [1, 6, 10], "blog_post": 6, "bloqu": 13, "blu": [4, 6, 12, 15], "bmp": 12, "boas": 2, "boat": 6, "bob": 6, "body": [4, 8, 13, 14, 16], "boilerplat": 5, "bold": 10, "bom": 2, "bonit": 6, "book": 6, "boolean": [6, 14], "booleans": 6, "boost": [6, 8], "bootstrap": 14, "bord": 16, "botar": 0, "both": [1, 2, 5, 6, 8, 12, 15], "bottl": [0, 4, 5, 8, 15], "bottle_app": 2, "bottleneck": 16, "bottlepy": [4, 5], "bottom": 12, "bot\u00e3": 3, "bot\u00f5": [3, 17], "boundari": 12, "boundary": 12, "box": [0, 6, 12], "br": [8, 10], "bracket": 8, "brackets": [8, 12], "branch": [1, 2], "break": [2, 6], "breaking": 6, "breaks": 12, "breez": 1, "brev": 4, "briefly": 2, "broken": [0, 6, 12], "brows": [1, 2, 3, 5, 7, 8, 14, 16], "browsers": 16, "browsing": 3, "bruc": [7, 14], "brut": 6, "bsd": 1, "buff": 6, "buffering": 4, "bug": 6, "bugs": 1, "build": [1, 2, 4, 5, 10, 14, 16], "building": [4, 12, 16], "built": [0, 1, 4, 6, 8, 12, 16, 17], "bulk_insert": 6, "bulletproof": 6, "bulm": [12, 14, 16], "bunch": 2, "busc": [3, 6, 14], "busing": 16, "but": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "button": [3, 4, 5, 12, 14, 16], "buttons": [10, 12, 14, 16], "by": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "bypass": [12, 13], "bypassed": 13, "byte": 12, "bytecod": 8, "bytes": 6, "b\u00e1sic": [0, 1, 6, 17], "c": [1, 2, 3, 5, 6, 8, 10, 12, 15], "ca": 12, "cabec": [10, 14], "cabe\u00e7alh": [4, 10], "cach": [0, 2, 4, 5, 12], "cache_db_select": 6, "cacheabl": 6, "cached": 5, "cache\u00e1vel": 6, "caching": [4, 6, 17], "cachorr": 11, "cad": [2, 4, 5, 8, 11, 14], "caiu": 6, "caix": [0, 6, 10], "calcul": 6, "calend": 14, "call": [0, 5, 6, 8, 12, 13, 16], "callabl": [10, 12], "callback": [6, 12, 13, 16], "callback_url": 13, "called": [1, 2, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "calling": [5, 6, 8], "calls": [2, 6, 8, 16], "cam": [6, 11], "caminh": [2, 4, 6, 14], "camp": [5, 7, 10, 12, 17], "can": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "cancel": [12, 16], "cancel_attrs": 16, "cannot": [2, 5, 6, 8, 12, 16], "capabiliti": [14, 16], "capac": 0, "capaz": [1, 2], "capitaliz": 16, "captur": 16, "cap\u00edtul": [2, 4, 5, 15], "car": 6, "caract": [6, 10], "caracter": 4, "caracter\u00edst": 17, "card": 6, "careful": [5, 6, 8, 15, 16], "carg": 6, "carl": 6, "carreg": [0, 6], "carroll": 0, "carry": 15, "cart": 5, "car\u00e1ct": [4, 6], "cas": [0, 1, 2, 3, 4, 5, 8, 10, 12, 13, 14, 15, 16], "cascading": 6, "cascat": 6, "case_sensitiv": 6, "cassi": 0, "caus": [6, 12], "caveat": [6, 8, 15], "caveats": 17, "cd": 2, "cdnjs": [8, 14], "celery": 17, "cent": [5, 6, 8], "century": 12, "ceo": [7, 14], "cerc": 6, "cert": [1, 2, 6, 14], "certain": [6, 8, 12, 14], "certez": 6, "certificat": 2, "certifiqu": [6, 11], "cf": 4, "cgi": 12, "cha": 6, "challeng": 13, "cham": [2, 4, 5, 7, 8, 11, 14], "chang": [1, 2, 3, 5, 6, 7, 8, 12, 13, 14, 15, 16, 17], "change_email": 13, "change_password": [8, 13], "changed": [2, 3, 4, 5, 6, 8, 12, 15, 16], "changed_fil": 4, "changing": 6, "channel": 1, "chapt": [3, 4, 5, 6, 8, 10, 12, 13, 14], "chapters": [1, 5], "char": 6, "charact": [6, 12], "characters": [6, 12], "charg": [2, 15], "chars": 13, "chat": 2, "chats": 1, "chav": [4, 8, 10, 11, 14], "chec": 11, "check": [3, 4, 5, 6, 12, 13, 14, 16], "check_": 16, "check_reserved": 6, "checkbox": [8, 10, 17], "checkboxwidget": 12, "checked": [10, 12, 13], "checking": [1, 12, 13, 15], "checks": [2, 6, 8, 12, 13], "cherry": 12, "chicag": 6, "children": 14, "choic": [1, 12, 14], "choos": [6, 12, 13], "choosen": 6, "chrom": [3, 16], "cient": 6, "cinc": 6, "circul": 6, "circumstanc": 5, "cit": [6, 10, 12], "cit0801": 7, "cit0802": 7, "cit1601": 16, "clar": 6, "clark": [7, 14], "clash": 12, "class": [5, 6, 8, 10, 12, 13, 15, 16], "class_inner_exceptions": 16, "class_styl": 14, "classific": [6, 14], "claud": 6, "claus": 6, "cle": 12, "clean": [5, 10], "cleanup": 0, "clearly": 16, "clev": 12, "cli": [1, 2], "clic": 14, "click": [3, 10, 12, 14, 16], "clickabl": 14, "clicked": [14, 16], "clicks": [6, 16], "client": [0, 7, 12, 13, 16], "client_id": 13, "client_ip": 5, "client_secret": 13, "clients": 5, "clientsid": 16, "cliqu": [3, 14], "clock": 12, "clon": [2, 4, 6, 16], "cloned": 2, "clos": [6, 10], "closed": [6, 8], "closing": [5, 6], "cloudflar": [8, 14], "cl\u00e1usul": [6, 8], "cmd": 2, "cn": 13, "co": [6, 12], "coa": 6, "cod": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "codific": 6, "coding": 1, "cois": 6, "colegial": 13, "collapsibl": 10, "collection": [0, 15], "collections": 2, "collects": 2, "colnam": 6, "coloc": 17, "colon": 8, "color": [4, 6, 8, 10, 12, 16], "colors": [4, 6], "cols": 10, "colspan": 10, "column": [6, 14], "columns": [6, 17], "colun": [6, 14], "comand": 6, "comando": [3, 4, 6, 10, 17], "combin": [4, 5, 10, 14], "combined": [5, 6, 12], "come": [0, 5], "comec": [1, 2, 4, 6], "comes": [0, 5, 8, 12, 13, 14, 15, 16], "comet": 6, "coming": 5, "comm": [2, 16], "command": [2, 4, 6, 8], "commands": [2, 4, 6, 8, 10], "comment": [6, 10], "commit": [5, 7, 14, 16], "commits": [5, 6], "committed": [6, 15], "common": [2, 4, 5, 8, 12, 13, 15, 16], "common_filt": 6, "common_filters": 6, "commonality": 8, "communicat": 5, "communication": 6, "communications": 5, "community": [0, 6], "compact": 6, "company": 14, "compar": [6, 13, 15], "compared": 0, "comparing": 13, "comparison": [12, 13], "compartilh": [5, 6], "compat": 6, "compatibility": 12, "compatibl": [0, 1, 10, 12], "competitor": 0, "compil": [4, 8, 10, 12], "compilation": 8, "compiled": [4, 8, 10, 12], "compiled_css": 4, "compiling": 8, "complet": [0, 2, 6, 10, 12, 14, 16], "completed": [0, 6], "completely": [6, 8, 13], "complex": [1, 2, 4, 5, 6, 8, 10, 13, 15, 16], "complexiti": 16, "complexity": 16, "compliant": 16, "complicated": 5, "component": [0, 4, 5, 8, 10, 12, 13, 16], "component_1": 16, "components": [0, 3, 10, 12, 16], "comport": [4, 6, 10, 14], "compos": 2, "composing": 5, "compost": [6, 10], "compreend": [1, 14], "compressed": 4, "comprim": 3, "compriment": 6, "compris": 3, "compromet": [4, 6], "compromis": 0, "comput": 1, "computed": 17, "computing": 16, "comp\u00f5": 6, "comum": [5, 6], "comunic": 6, "comuns": 3, "concaten": 6, "concatenat": 10, "concatenating": 10, "conceived": 6, "concept": [0, 13], "conch": 2, "concorrent": 0, "concurrency": [6, 16], "concurrent": 16, "concurrently": [2, 12, 16], "cond": 5, "condicion": 8, "condicional": 6, "condition": [6, 12, 13, 17], "conditions": [6, 12], "condi\u00e7\u00e3": 6, "condi\u00e7\u00f5": 6, "conect": [6, 13], "conex\u00e3": 4, "conf": 6, "confiabil": 1, "config": [2, 4], "configur": [3, 13, 14, 15, 16, 17], "configuraiton": 4, "configuration": [1, 4], "configurations": 2, "configured": [5, 12, 15], "confirm": [2, 12, 14], "confirmation": 16, "confirmations": 16, "confirms": 13, "conflicts": 6, "conflit": [0, 2, 4, 6], "conform": [6, 13], "confund": 6, "confus": 6, "conhec": [1, 6], "conjunt": [6, 10, 11, 14], "conn": 5, "connect": [3, 6], "connecting": 6, "connection": [5, 6, 12], "connectionpool": 6, "connections": [5, 6, 15], "cons": 6, "conseg": 6, "consegu": 6, "consequenc": [6, 12, 16], "consequently": 12, "consid": [5, 8, 10, 12, 14, 16], "consider": [6, 8, 10], "considered": [5, 6, 8, 15], "consist": [6, 15], "consistency": 8, "consistent": 5, "consists": [6, 14], "consol": [2, 10, 13, 16], "constant": 12, "constraining": 15, "constru": [6, 10, 14], "constructing": 6, "construction": 4, "constructor": [6, 13, 17], "constructors": [6, 12], "construtor": [5, 10, 12, 17], "constru\u00e7\u00e3": [6, 10, 14], "constru\u00edd": [6, 14], "constr\u00f3": 6, "consult": [1, 7, 13, 14], "consulta1": 6, "cont": [2, 5, 10, 11, 13], "contador": [5, 6], "contag": 6, "contain": [2, 4, 5, 6, 8, 10, 12, 13, 16], "contained": [10, 12, 14], "containing": [0, 2, 5, 12, 13, 14, 16], "contains": [3, 5, 7, 8, 11, 12, 14, 15, 16], "contect": 8, "contenh": 5, "content": [2, 4, 5, 6, 8, 10, 12, 14, 15, 16], "content_typ": 16, "contents": [8, 10, 16], "context": [3, 5, 6, 8, 13], "contextlib": 6, "contexts": 5, "conte\u00fad": [4, 6, 10], "continu": [0, 6, 8, 16], "contr": 6, "contribu": 17, "contributed": 0, "control": [2, 8, 12, 13, 14, 16], "controll": [5, 6, 8, 10, 12, 13, 14, 15, 16], "controllers": [4, 6, 8, 10, 12, 15, 16], "controls": 12, "contr\u00e1ri": [0, 4, 6, 8], "cont\u00e9m": [2, 4, 5, 6], "cont\u00eain": 6, "cont\u00eam": [6, 11], "convenienc": 5, "convenient": [6, 17], "convention": [8, 12, 15], "conventions": [4, 6], "conven\u00e7\u00e3": 4, "conversion": [12, 17], "convers\u00e3": 6, "convert": [4, 6, 10], "converted": [1, 6, 12], "converting": 6, "converts": 12, "cooki": [0, 4, 6, 12, 16], "copi": 2, "copied": 2, "copy": [1, 2, 3, 4, 6, 14], "copyfileobj": 6, "copying": [2, 8, 10, 17], "cor": [1, 2, 4, 5], "cord": [4, 6, 11], "corey": 1, "corn": 14, "cornerston": 0, "corp": [8, 10], "corr": 17, "correct": [4, 13], "correct_cod": 13, "correctly": [4, 12], "corrent": 6, "correspond": [2, 4, 6, 11], "correspondent": [4, 6, 10], "corresponding": [3, 6, 13, 14, 15], "corresponds": [2, 6, 10], "corret": 6, "corrig": 1, "corromp": 6, "corrupted": 6, "corruption": 6, "costum": 6, "cot": 6, "cota\u00e7\u00e3": 6, "couchdb": 6, "couchdbadapt": 6, "could": [1, 2, 4, 6, 8, 10, 12, 13, 15], "count": [5, 7, 13, 14, 16], "coupl": 16, "cours": [1, 8], "cov": 4, "cp": 2, "creat": [0, 1, 2, 4, 5, 6, 8, 12, 13, 14, 16], "create_form": 12, "create_thing": 12, "created": [0, 2, 4, 5, 6, 8, 12, 13, 16], "created_by": 6, "created_on": 6, "creating": [1, 2, 5, 6, 13, 15, 16, 17], "creation": 6, "creativ": 12, "creativity": 13, "credential_decod": 6, "cresc": 6, "cri": [2, 3, 4, 5, 6, 8, 10, 13, 14], "cria\u00e7\u00e3": [2, 6, 14], "crit": 12, "critical": 2, "crit\u00e9ri": 6, "cross": [5, 10], "crossorigin": 8, "crt": [2, 6], "crud": [3, 12, 16], "cruz": [1, 6], "crypt": 2, "cs": 5, "csrf": [5, 12], "csrf_protection": 12, "csrf_session": 12, "css": [1, 4, 5, 8, 10, 12, 13, 14, 16], "csv": 15, "ct": 5, "ctrl": [2, 3], "cubrid": 6, "cubridadapt": 6, "cubriddb": 6, "cuj": 6, "current": [5, 6, 10, 13, 14, 15, 16], "current_record": 6, "currently": [2, 5, 8], "curs": 1, "cursor": [6, 16], "curt": 6, "custom": [0, 4, 5, 6, 8, 10, 13, 15, 16, 17], "custom_check": 12, "custom_qualifi": 6, "customiz": [8, 12, 14], "customizabl": [12, 14], "customization": 12, "customizing": 17, "cx_oracl": 6, "c\u00edclic": 6, "c\u00f3dig": [4, 6, 8, 10], "c\u00f3p": 6, "d": [2, 7, 10, 12], "dad": [0, 1, 3, 4, 5, 13, 17], "daemon": 2, "daemons": 16, "dal": [2, 4, 7, 12, 13, 14, 16, 17], "dals": 6, "dan": 0, "danc": 13, "dand": 4, "dangerous": 15, "daquel": 6, "dar": 6, "dash": 12, "dashboard": [2, 4, 5, 8, 12, 16, 17], "dashboard_mod": 2, "dat": [0, 2, 5, 6, 7, 8, 10, 13, 14, 15, 16], "data_label": 16, "databas": [0, 1, 3, 7, 13, 14, 15, 16, 17], "datalist": 16, "datetim": [4, 5, 6, 7, 12, 16], "datetimewidget": 12, "day": 12, "days": 12, "db": [2, 3, 4, 5, 7, 10, 12, 13, 14, 15, 16], "db1": 6, "db2": 6, "db2adapt": 6, "db2ibm": 6, "db2pyodbc": 6, "db_a": 6, "db_b": 6, "db_codec": 6, "db_fold": [5, 7, 14], "db_nam": 6, "db_uid": 6, "dbadmin": 4, "dbi": 12, "dbo": 6, "dbset": 12, "dbstor": 5, "dc": 13, "dd": 12, "deal": [6, 16], "dealfar": 0, "debounc": 16, "debug": [1, 2, 5, 6, 8], "debugg": 1, "debugged": 8, "debuggers": 15, "debugging": [1, 4], "decid": [6, 12, 13, 14], "decim": 4, "decimal": [6, 12], "decimals": 12, "decl": [4, 6], "declar": [0, 4, 5, 6, 8], "declared": 5, "decod": 6, "decode_credentials": 6, "decoded": 6, "decomp\u00f5": 6, "decor": [1, 4, 17], "decorated": 15, "decorator": [4, 5, 6, 15], "decorators": [5, 15], "decresed": 13, "dedic": [1, 4, 15], "dedicated": [1, 5, 10], "def": [4, 5, 6, 7, 10, 12, 13, 14, 15, 16], "default": [2, 3, 4, 5, 6, 7, 10, 12, 13, 14, 15, 16], "defaults": [5, 6, 12, 14, 15], "deferred": [6, 16], "defin": [0, 4, 5, 8, 10, 12, 13, 14, 16], "define_tabl": [5, 7, 12, 13, 14, 15], "defined": [2, 4, 5, 6, 8, 12, 13, 14, 15, 16], "defining": 14, "definit": 12, "definition": [6, 7, 12, 14], "definitions": [6, 7, 15], "defini\u00e7\u00e3": 6, "defini\u00e7\u00f5": 6, "deform": 11, "deix": 13, "del": [5, 6, 10], "delay": 16, "delaying": 16, "deleg": 6, "delet": [2, 4, 7, 12, 14, 16], "deletabl": [12, 14], "delete_record": 6, "deleted": 6, "deleting": [6, 14], "deletion": 6, "deletions": 6, "delimit": [0, 6, 8], "delimiters": [5, 15], "deliv": 16, "delt": 16, "dem": [1, 2, 16], "demand": 6, "denormaliz": 7, "denormalization": 6, "dentr": [0, 2, 4, 6, 14], "deny": 7, "depend": [4, 5, 6, 13], "dependenc": [0, 2, 5, 15], "dependent": [2, 3, 4, 5], "depending": [6, 8, 12, 14], "depends": [5, 6, 15], "deploy": 2, "deployment": 1, "deployment_tools": 2, "depo": [2, 3, 6], "deprecated": [12, 16], "depur": [6, 8], "deriv": 6, "derived": [6, 12], "desat": 6, "desativ": 6, "desc": 14, "descart": 6, "descendant": 10, "descobert": [2, 11], "descompact": 2, "descrev": 6, "describ": [3, 12], "described": [2, 4, 5, 6, 12], "description": [6, 7, 12, 13], "descriptiv": 12, "descrit": [2, 4, 6, 14], "desd": [5, 6], "desej": [1, 2, 5, 6, 13, 14], "desempenh": 6, "desencad": 3, "desenvolv": 0, "desenvolvedor": [1, 8, 13, 14], "desfaz": 6, "design": [0, 13, 16, 17], "designed": [0, 4, 6, 8, 12, 16], "desir": 8, "desired": [5, 12], "desloc": 6, "desnormaliz": 7, "despej": 6, "dess": [0, 5, 6, 13], "dest": [4, 6, 12], "detail": [6, 10, 14, 16], "detail_fields": 12, "detailed": [2, 14], "details": [0, 1, 5, 6, 7, 8, 12, 14], "detalh": [6, 14], "determin": [4, 5, 6, 11, 12, 13, 14], "determined": [8, 12], "determining": 5, "deterministic": 5, "dev": [1, 2, 3, 4, 5, 6, 8, 10, 13, 14], "develop": [5, 6, 8, 13, 14, 15], "developers": [0, 1, 6, 13, 16], "developing": 4, "development": [0, 1, 4, 6], "development_tools": 2, "devic": 8, "devolv": [6, 8], "di": [0, 5], "diagr": 7, "dialect": 6, "dialects": 6, "dialet": 6, "dic": 17, "dicion\u00e1ri": [10, 11, 12], "dict": [4, 5, 6, 11, 12, 14, 15, 16], "dictionari": 6, "dictionary": [4, 5, 6, 10, 12, 15], "did": [0, 4, 16], "dif": 6, "diferenc": 6, "diferent": [2, 5, 6, 11, 14], "diff": 6, "differenc": [6, 12, 15], "different": [0, 2, 4, 5, 6, 8, 12, 13, 15, 16], "differs": [6, 14], "difficult": [2, 16], "dif\u00edcil": 1, "dig": 6, "digit": [12, 13], "dimensions": 12, "dinam": 10, "din\u00e2m": [6, 17], "dir": [2, 12, 16], "direct": 6, "directiv": [2, 4, 8, 12], "directly": [2, 4, 6, 7, 10, 12, 13, 14, 16], "directory": 13, "direit": [3, 6], "diret": [1, 5, 6, 8], "diret\u00f3ri": 4, "dirnam": [5, 7, 14], "disabl": [6, 14], "disabled": [7, 10, 12], "disallow": 12, "discord_client_id": 13, "discord_client_secret": 13, "discount": 6, "discounted_total": 6, "discounted_total_pric": 6, "discounted_unit_pric": 6, "discovered": 13, "discriminator": 13, "discuss": 3, "discussed": [6, 8, 10, 12], "discussion": 6, "discuss\u00e3": 1, "discuss\u00f5": 1, "discut": 6, "disk": 5, "dismissal": 15, "dismissibl": 5, "dispar": 6, "display": [5, 12, 13, 14, 16], "displayed": [3, 6, 10, 12, 14, 16], "displaying": [5, 12, 14], "displays": [13, 14], "dispon": 6, "dispon\u00edv": 1, "disposit": [4, 5, 13], "diss": [4, 6, 14], "distinct": [8, 12], "distinction": 6, "distin\u00e7\u00e3": 6, "distribu": 6, "distribut": 6, "distributed_transaction_commit": 6, "distribution": 6, "ditched": 0, "div": [4, 5, 8, 12, 14, 15, 16], "divisibl": [8, 12], "division": [8, 10], "divis\u00e3": 8, "divmod": 6, "diz": [4, 6, 13, 14], "djang": [0, 1, 6, 15], "do_connect": 6, "dobr": 6, "dobrag": 6, "doc": 1, "dockerfil": 2, "docs": [1, 2, 4, 16], "doctor": 6, "doctyp": [8, 14], "document": [6, 8, 10, 12, 16], "documentation": [3, 12, 16], "documentations": 5, "documented": [5, 12], "does": [0, 2, 5, 6, 8, 12, 13, 14, 15, 16], "doesn": 5, "dog": [11, 12, 16], "doh": 12, "doing": [6, 12, 16], "dois": [0, 5, 6, 8], "dom": [8, 17], "domain": [12, 13, 17], "domains": 4, "don": [2, 4, 5, 6, 8, 12, 13, 14, 15], "dot": 12, "doubl": [2, 6, 8], "doubt": 14, "down": [12, 16], "download": [2, 12], "download_url": 12, "downs": 6, "downsid": [6, 15], "dramat": 6, "driv": 6, "driven": 0, "driver_args": 6, "drivers": 6, "drop": 12, "dropdown": [12, 14, 16], "dropdowns": 16, "dropping": 6, "dsn": 6, "duas": [5, 6], "due": [6, 7, 12], "dummy": [6, 8], "dummyrespons": 8, "dump": 5, "dumpfil": 6, "dumps": [6, 16], "duplicat": 6, "durability": 7, "during": [6, 14], "dynamic": [4, 8, 13], "dynamically": [6, 7, 8, 12], "d\u00e1": [0, 2, 14], "d\u00edgit": [4, 6], "ea": 6, "each": [2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "earli": [13, 16], "early": 5, "easi": [7, 8], "easiest": 6, "easily": [4, 5, 6, 8, 10, 12, 14], "easy": [5, 8, 12], "ebook": 1, "echo": 4, "ecosyst": 16, "edge": 16, "edif\u00edci": 5, "edit": [0, 2, 3, 4, 8, 12, 13, 14, 15, 16], "edit_sidec": 16, "editabl": [14, 15], "editing": [8, 10, 12, 13, 14], "editor": [1, 8], "editors": 8, "edi\u00e7\u00e3": 0, "edi\u00e7\u00f5": 3, "education": 2, "efeit": 6, "efet": 6, "effect": [3, 5, 6, 12], "efficient": [0, 5], "efficiently": [1, 15], "efforts": 1, "efg": 10, "eficient": 6, "eith": [6, 12], "el": 10, "element": [6, 8, 10, 12, 14, 16], "elements": [6, 10, 12, 16], "elev": 6, "elimin": [6, 14], "else": [2, 5, 6, 12, 13, 14, 15, 16], "elt": 16, "el\u00e9tr": [4, 5, 13], "emacs": 8, "email": [2, 8, 12, 13, 15, 16], "emails": [12, 16], "emails_onvalidation": 12, "embed": 10, "embedded": [5, 8, 12], "embedding": 8, "ember": 12, "embor": 6, "emerging": 16, "emit": [3, 6], "employ": 5, "employe": 14, "empreg": 14, "empres": 14, "empty": [2, 4, 12, 16], "empty_regex": 12, "en": [5, 7, 16], "enabl": [1, 5, 6, 7, 10, 13, 15, 16], "enable_record_versioning": 6, "enabled": [5, 7, 13, 15], "encaix": 4, "encapsulat": [8, 10], "encerr": 8, "enclosed": 6, "enclosing": 8, "encod": 6, "encoded": [5, 6, 12, 16], "encoding": 6, "encontr": [4, 6, 8, 11, 13], "encrypted": [2, 5], "encryption": 0, "end": [4, 6, 8, 12, 13, 14, 16], "enderec": 6, "ending": 8, "endpoint": [13, 16], "ends": [6, 8, 15], "enforc": [5, 12], "enforced": [6, 12, 15], "enfrent": [1, 6], "engin": [6, 12], "engineering": 2, "english": 5, "enough": [12, 13], "enquant": [0, 6, 8], "enqueu": 16, "enqueue_run": 16, "enqueueing": 16, "ensin": 13, "ensur": [4, 12], "entant": [6, 8, 10, 14], "entend": 6, "enter": [4, 12, 13], "entered": [10, 13], "entering": 5, "enterpris": [1, 13], "entidad": 6, "entir": [8, 15, 16], "entirity": 4, "entity_quoting": 6, "entrad": [4, 6, 10, 11], "entri": [7, 12, 14], "entropy": 12, "entry": [0, 6, 15], "ent\u00e3": [2, 4, 6, 13], "env": [1, 15], "envelop": 16, "envi": [10, 14], "environ": [5, 15], "environment": [0, 1, 4, 5, 6], "environments": [1, 4], "envolt": 11, "envolv": 6, "eo": 6, "epub": 1, "eq": 7, "equal": [7, 12, 13, 16], "equals": [10, 13], "equip": 5, "equivalent": [4, 5, 6, 10, 12, 15], "equivalently": 10, "errad": 6, "errlog": 5, "erro": [2, 3, 6], "error": [2, 3, 5, 6, 7, 8, 10, 12, 16], "error_messag": 12, "errorlog": 2, "errors": [1, 5, 6, 7, 12, 15], "escap": [6, 8, 10], "escaped": [6, 8, 10], "escaping": [6, 8], "escolh": [1, 4, 6], "escond": 6, "escrav": 6, "escrev": [6, 8, 10], "escrit": [6, 8], "espac": [6, 11, 14], "espec": [5, 6, 10], "especial": [2, 4, 6, 11], "especializ": 6, "especially": [0, 2, 4, 5, 12, 13], "especif": [4, 6], "especific": [4, 5, 6], "especifiqu": 14, "espec\u00edf": [1, 2, 6], "esper": [4, 5, 6], "esprim": 4, "esquec": 6, "esquem": 6, "esquerd": [6, 14], "estabelec": 6, "establish": 6, "established": 6, "establishing": 15, "estad": [5, 6], "estam": 6, "estar": [4, 6, 10], "estend": [0, 5, 6], "estil": 14, "estiv": 6, "estrangeir": [1, 14], "estreit": 6, "estrutur": [4, 5, 6, 11], "est\u00e1t": [10, 17], "est\u00e3": [5, 6, 7, 10, 13], "etap": 6, "etc": [4, 5, 6, 7, 11, 15], "etiquet": [6, 10], "eval": 16, "evaluat": [5, 12, 16], "evaluated": [6, 7], "even": [1, 4, 5, 6, 8, 10, 12, 14, 15, 16], "event": 6, "event_tim": 6, "events": [2, 16], "eventually": 5, "ever": 6, "every": [0, 5, 6, 13, 14, 15, 16], "everyon": [0, 1], "everything": [2, 15, 16], "evit": [0, 2, 6, 8, 12], "evolution": 0, "ex": 6, "exact": [8, 15], "exactly": 5, "exampl": [1, 2, 4, 5, 6, 8, 10, 11, 13, 17], "exat": [6, 8], "excellent": [1, 14], "except": [2, 5, 6, 8, 10, 12, 13, 16], "exception": [5, 6, 12, 13, 16], "exceptions": [4, 5, 16], "excep\u00e7\u00e3": 8, "excerpt": 8, "excet": [5, 6], "exce\u00e7\u00e3": [4, 5, 6, 8], "exce\u00e7\u00f5": 4, "exclu": 6, "exclud": 12, "exclus": 6, "exclusiv": [12, 13], "exclusively": [0, 5, 6], "exclus\u00e3": 14, "exclu\u00edd": [2, 6], "exe": 2, "execu": [2, 4], "execut": [0, 2, 3, 6, 12], "executabl": [6, 10], "executed": [2, 5, 6, 8, 15, 16], "executing": 16, "execution": [1, 16], "exempl": [2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13], "exercis": 5, "exerc\u00edci": 13, "exib": [3, 6, 8, 10, 14], "exibi\u00e7\u00e3": [13, 14], "exig": [1, 2, 6, 13], "exist": [1, 2, 4, 5, 6, 8, 12, 16], "existenc": 13, "existent": [2, 6, 13, 14], "existing": [2, 5, 6, 12], "exists": [5, 6], "exit": 2, "exiting": 5, "exp": [4, 6], "expand": 3, "expect": [6, 12], "expected": 15, "expects": [2, 10], "experienc": [1, 13], "experienced": 0, "experiment": [1, 12], "experimental": [2, 5], "experimenting": 1, "expir": 5, "expiration": [5, 6], "explain": 12, "explained": [5, 6, 12, 14], "explanatory": 6, "explic": [4, 6], "explicit": [0, 2, 4, 5, 6, 8, 12], "explicitly": [0, 2, 5, 6, 8, 10, 12, 16], "explict": 6, "exploring": 3, "expl\u00edcit": [6, 13], "expor": [4, 13], "export": 17, "export_to_csv_fil": 6, "exporting": 6, "expos": [4, 5], "exposed": 6, "expost": 7, "express": 12, "expressed": 12, "expression": [4, 6, 8, 11, 12, 14], "expressions": [10, 12, 16], "express\u00e3": [6, 7, 11, 14], "express\u00f5": 11, "exp\u00f5": [0, 3, 4, 6, 13], "extend": [5, 6, 12, 13, 15, 16], "extended": 8, "extends": [6, 8, 16], "extensibl": 15, "extension": [12, 15], "extensions": 12, "extensively": [3, 6], "extens\u00e3": 6, "extern": 2, "external": [6, 13], "externally": 4, "extra": [5, 6, 12, 13], "extra_fields": 5, "extract": [6, 12], "extracted": 6, "extracts": 16, "extras": 6, "extra\u00edd": 6, "extrem": 6, "f": [6, 13, 14, 15, 16], "fa": 14, "facebook": [0, 5], "facil": [4, 5, 6], "facilitat": 4, "fact": [4, 5, 15], "factori": [5, 10], "fail": [6, 8, 12, 13, 16], "failed": [6, 13], "fails": [3, 12], "failur": [6, 12, 16], "fak": 6, "fake_migrate_all": 6, "falh": 4, "fall": 12, "fals": [2, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "falt": 6, "famous": [1, 6], "fancy": 16, "far": [14, 16], "fas": 6, "fast": [0, 4, 6, 8, 15, 16], "fat": 6, "fath": 6, "father_id": 6, "favorite_color": 5, "faz": [2, 5, 8, 10, 12, 13], "fb00": 12, "fdb": 6, "fe80": 12, "feasibl": [2, 12], "featur": [0, 4, 6, 7, 8, 15, 17], "february": 5, "fech": [6, 8, 10], "fechament": 10, "feit": 6, "fetch": [6, 15, 16], "fetchon": 6, "few": [8, 13, 16], "fez": [2, 6], "ff00": 12, "fic": 6, "ficheir": 6, "fict\u00edc": 6, "fict\u00edci": 6, "fid": 6, "field": [2, 4, 5, 7, 13, 14, 15, 16, 17], "field1": 6, "field2": 6, "field3": 6, "field_id": 14, "fieldnam": [6, 16], "fields": [5, 7, 12, 13, 14, 15, 17], "fieldstorag": 12, "fifth": 14, "fil": [0, 1, 2, 3, 5, 6, 8, 10, 11, 13, 14, 16, 17], "file_content": [6, 16], "file_nam": [6, 16], "file_path": 15, "fileir": 6, "filenam": [2, 4, 5, 6, 12], "filep": 4, "filepaths": 4, "filesyst": [4, 5, 6], "fileuploadwidget": 12, "fill": [12, 16], "filled": 12, "filt": [2, 4, 7, 12, 14], "filter_in": 12, "filter_out": 14, "filtered": 13, "filters": [4, 12], "filtr": [13, 14], "filtrag": [4, 14], "fim": [1, 2, 5, 6], "fin": [2, 13, 14], "final": [1, 2, 5, 6, 11, 13], "finally": [4, 14], "find": [1, 2, 4, 12, 13, 14], "find_by_tag": 13, "find_match": 11, "findall": 12, "finding": 14, "finds": [6, 16], "fins": 6, "firebird": 6, "firebird_embedded": 6, "firebirdadapt": 6, "firebirdembedded": 6, "firebirdembeddedadapt": 6, "firefox": [3, 16], "firfox": 16, "first": [0, 2, 3, 5, 7, 8, 10, 12, 13, 14, 15, 16], "first_nam": [4, 5, 13, 14], "first_only": 10, "first_row": 6, "first_row_dict": 6, "fist": 12, "fits": 12, "fix": [5, 6], "fixed": [6, 15], "fixtur": [0, 2, 6, 10, 13, 15, 17], "fixur": 17, "fiz": 1, "fk_field": 16, "fk_tabl": 16, "fkdaog": 8, "flag": 12, "flash": [8, 12, 16, 17], "flask": [0, 15], "flexibil": [0, 14], "flexibility": 16, "flexibl": [6, 13], "flex\u00edv": 0, "flex\u00edvel": 6, "flight": 7, "float": [4, 12], "floating": 12, "flow": 13, "flux": 6, "fn": 2, "focus": 16, "fold": [1, 2, 3, 4, 5, 6, 7, 11, 12, 14, 15], "folders": 2, "follow": [2, 4, 7, 14, 15], "followed": [2, 5, 12], "following": [1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "follows": [5, 6, 12], "font": [6, 8, 12, 14], "foo": 14, "foot": 8, "footers": 8, "footing": 13, "forbid": 12, "forbidden": 12, "forc": [2, 5, 6, 7, 12, 13], "forcing": 12, "foreground": 12, "foreign": 14, "foreign_key_checks": 6, "forgery": 5, "forget": 14, "form": [0, 4, 5, 6, 7, 11, 13, 14, 17], "form_basic": 12, "form_custom_widgets": 12, "form_exampl": 12, "form_minimal": 12, "form_nam": 12, "form_widgets": 12, "format": [4, 5, 7, 10, 11, 13, 14], "formats": 12, "formatt": 16, "forma\u00e7\u00e3": 6, "formdat": 7, "forms": [6, 10, 14, 15, 16], "formstyl": [12, 14, 16], "formstylebootstrap4": 12, "formstylebulm": [12, 14, 16], "formstyledefault": [12, 14], "formstylefactory": 16, "formul\u00e1ri": [6, 13, 14, 17], "fornec": [0, 2, 3, 4, 5, 6, 10, 13, 14], "forum": 3, "foruml\u00e1ri": [6, 10, 17], "forward": 1, "found": [2, 5, 6, 12], "four": [2, 6], "fourth": 12, "fp": 5, "fr": 11, "fracass": 6, "framework": [0, 2, 12, 13, 14, 15, 16], "frameworks": [0, 2, 5, 6, 15, 16], "fras": 6, "fre": [1, 5, 13], "freetext": 16, "frent": 8, "frequently": 12, "frequ\u00eanc": 1, "friendly": [0, 5, 8], "from": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "from_addr": 16, "from_address": 13, "from_adds": 16, "from_email": 16, "front": [4, 12, 16], "frontends": 16, "fronts": 0, "fsstorag": 5, "ftps": 12, "fug": [6, 8], "full": [2, 4, 6, 8, 12, 16], "fullnam": 6, "fully": [4, 5, 6, 12], "func": [2, 5], "funcion": [6, 17], "funcional": [0, 5, 6], "function": [2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "functional": 16, "functionaliti": 2, "functionality": [0, 8, 15], "functions": [2, 5, 10, 16], "functools": 16, "fund": 4, "fun\u00e7\u00e3": [4, 6, 8, 10], "fun\u00e7\u00f5": [4, 8], "futur": [5, 15, 16], "f\u00e1bric": 6, "f\u00e1cil": [0, 6, 8, 10, 13], "f\u00edsic": 13, "g": [1, 2, 6, 10, 12], "gain": 2, "gananc": 4, "garant": [6, 8], "garraf": 4, "gavgavian": 1, "gend": 6, "general": [5, 6, 12, 13], "generaliz": 6, "generally": [2, 5, 6, 8], "generat": [2, 6, 8, 10, 12, 13, 14, 15, 16], "generate_time_based_cod": 13, "generated": [1, 5, 6, 8, 12, 13, 14, 16], "generation": 1, "generator": 10, "generic": [2, 4, 5, 6, 12], "generically": 6, "genindex": 17, "gen\u00e9r": 6, "ger": [6, 8, 10, 13, 14], "gerador": 6, "geral": [0, 2, 6, 7, 10], "german": 11, "gest\u00e3": [0, 1], "get": [1, 2, 4, 5, 6, 8, 12, 13, 14, 15, 16, 17], "get_cooki": 16, "get_us": [4, 5, 13, 15], "get_vars": [7, 15], "gets": [8, 13], "getvalu": 6, "gevent": [1, 2], "geventwebsocketserv": 2, "geventws": 2, "gia": 5, "gib": 6, "gif": 12, "git": [1, 2], "github": [2, 3, 4, 5, 6, 14], "gitlat": 1, "giv": [4, 5, 6, 8, 13], "given": [2, 5, 6, 8, 12, 16], "giving": 5, "global": [0, 6, 15, 16], "globally": [2, 6], "globals": [5, 6, 8, 13, 15], "go": [2, 4, 14], "goes": [6, 12], "going": [4, 16], "good": [3, 12], "googl": [0, 3, 5, 8, 12, 14], "googledatastor": 6, "googledatastoreadapt": 6, "googlemysql": 6, "googlepostgr": 6, "googlesql": 6, "googlesqladapt": 6, "gost": 6, "got": 6, "gotch": 6, "gott": 8, "grac": 6, "grad": 0, "grand": 6, "granul": 6, "granulary": 12, "graphically": 6, "graphql": 7, "grau": 6, "grav": 6, "grava\u00e7\u00f5": 12, "gravidad": 6, "grav\u00e1vel": 6, "great": 16, "green": [4, 6, 12, 15], "grelh": 14, "grid": [0, 4, 17], "grid_class_styl": 14, "grid_tutorial": 14, "gridactionbutton": 14, "gridclassstyl": 14, "gridclassstylebulm": 14, "grids": 17, "group": [1, 3, 5, 13, 15], "group_nam": 13, "groupby": 12, "grouping": 2, "groups": [0, 1, 3, 5, 13, 15], "growing": [0, 16], "grup": [6, 13], "gt": [7, 10], "guarant": 5, "guaranteed": [6, 15, 16], "gui": 3, "guid": 2, "guidelin": 13, "gunicorn": 2, "gunicorngevent": 2, "gz": 12, "h": [2, 12], "h1": [4, 8], "h2": [8, 12], "habilit": 6, "had": 6, "hamburg": 8, "hand": [2, 16], "handl": [2, 4, 5, 6, 15, 16], "handled": [4, 5, 12, 16], "handlers": 4, "handling": [4, 14], "handy": 6, "hanging": 1, "happen": 5, "happens": 6, "hard": 15, "hardcod": 5, "harmoniz": 4, "has": [0, 2, 4, 5, 6, 8, 12, 13, 14, 15, 16], "has_membership": 13, "hash": [2, 6, 12], "hashed": 12, "hav": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "head": [4, 5, 6, 8, 14, 16], "headers": [4, 8, 16], "heading": 12, "headings": [10, 14], "height": [12, 14, 16], "hell": [4, 5, 6, 8, 10, 12, 13, 16], "help": [1, 2, 5, 6, 7, 8, 10, 12, 14, 15], "helpers": [4, 6, 8, 12, 14, 15, 16, 17], "helps": [6, 8, 12], "henc": [0, 2, 5, 6, 12, 13], "her": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 16], "herd": 6, "her\u00f3": 7, "hesitat": 6, "hex": 12, "hh": 12, "hi": 16, "hid": 14, "hidden": [12, 16], "hidden_div": 16, "hidden_input": 16, "hierarchical": [6, 13], "high": [12, 13], "highest": 12, "highlighting": [1, 8], "highly": [1, 4, 14], "hints": 14, "his": 12, "historical": 0, "history": 12, "hist\u00f3r": [5, 6], "hmac": 12, "ho": 5, "hold": 16, "holds": 6, "hom": 8, "hom\u00f3log": 4, "hor": 6, "hosped": 1, "host": [2, 4, 5, 12, 13], "hosted": 1, "hour": 12, "hous": 15, "housekeeping": 6, "houv": 6, "how": [0, 2, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "howev": [6, 8, 12, 13, 16], "href": [8, 10, 13, 14], "hs256": 5, "html": [1, 2, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16], "html5": 8, "htmx": [14, 17], "htmx_form": 16, "htmx_form_dem": 16, "htmx_grid": 16, "htmx_list": 16, "htmxautocompletewidget": 16, "http": [0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], "https": [1, 3, 4, 5, 7, 8, 12, 13, 14, 16], "httrespons": 5, "hulk": 12, "hundreds": 16, "hx": 16, "hypertext": 16, "hyphen": 10, "h\u00e1bit": 2, "h\u00edfens": 10, "i": [2, 4, 5, 6, 7, 8, 12, 13, 16], "i18n": [5, 11], "ibm_db_dbi": 6, "icon": [8, 14], "icons": 14, "id": [2, 7, 8, 10, 12, 13, 14, 15, 16], "id1": 6, "id2": 6, "id_field_nam": 14, "id_valu": 14, "ide": [0, 1, 2, 3, 6], "ident": 7, "identical": 15, "identifi": 6, "identific": [6, 10], "identify": 4, "identifying": [5, 12], "identity": [6, 7, 15], "ides": 15, "idiom": 11, "idn": 12, "ids": [6, 14], "ie": 12, "ietf": 12, "if": [0, 1, 2, 3, 4, 5, 6, 7, 10, 12, 13, 14, 15, 16], "ifram": 12, "ignor": [5, 6, 8, 12, 14], "ignore_attribute_plugin": 14, "ignore_common_filters": [6, 12], "ignore_field_cas": 6, "ignored": [4, 8, 10, 14, 16], "igual": [6, 7], "iip": 6, "illustrat": 6, "ilustr": [6, 8], "imag": [6, 8, 10, 12], "image_fil": 6, "imagin": [5, 6, 8, 16], "imaging": 12, "imap": 6, "imapadapt": 6, "imaplib": 6, "imediat": 14, "img": [4, 12], "immediat": 2, "immediately": 6, "immensely": 0, "imped": [6, 10], "impersonat": 13, "impersonating": 13, "implement": [5, 6, 8, 13], "implementation": [4, 8, 12, 15], "implementations": 16, "implemented": 6, "implements": [5, 6], "impli": 7, "implications": 5, "implicit": 6, "implicitly": [6, 12], "impl\u00edcit": 6, "impor": 6, "import": [0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17], "import_and_sync": 6, "import_from_csv_fil": 6, "important": [0, 2, 4, 5, 6, 12, 13], "imported": [2, 12, 13, 15], "importing": 6, "impos": 8, "impot": 12, "imprim": 6, "improved": 12, "improvement": 14, "improving": [12, 13], "in": [0, 1, 2, 3, 4, 6, 7, 11, 12, 13, 14, 15, 17], "inalter": 6, "inclu": [0, 4, 6, 8, 10, 14], "includ": [4, 6, 7, 10, 14, 16], "include_action_button_text": 14, "include_paths": 4, "included": [6, 8, 12, 14], "including": [5, 12, 14, 15, 16], "inclusion": 0, "inclusiv": [0, 12], "inclus\u00e3": 10, "inclu\u00edd": [6, 10], "incoming": 2, "incomum": 6, "incorpor": 6, "incorret": 8, "increased": 5, "increment": 6, "indeed": [0, 5], "indent": 8, "indentation": 8, "indented": 8, "independent": [0, 5, 6, 13], "indesej": [2, 6], "index": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "indic": [4, 6], "indicat": [12, 13, 15], "indicator": 16, "individu": [0, 2, 5, 6], "individual": [5, 6, 7, 8, 12], "indo": 6, "inefficient": 5, "ineficient": 6, "infinit": 6, "info": [2, 4, 5, 13], "inform": [1, 4, 5, 6, 12], "information": [5, 6, 7, 12, 13, 15, 17], "informed": 6, "informix": 6, "informixadapt": 6, "informixdb": 6, "informixs": 6, "ingredient": 4, "ingres": 6, "ingresadapt": 6, "ingresdb": 6, "ingresu": 6, "ingresunicod": 6, "ingresunicodeadapt": 6, "inic": [2, 4], "inicializ": [4, 5], "init": [4, 14], "initial": [8, 12], "initializ": 2, "initialized": 0, "inject": [12, 14, 17], "injected": [5, 8, 10, 12], "injecting": 10, "injection": 6, "injections": 10, "inje\u00e7\u00e3": 6, "inlin": 16, "inner": [5, 16], "input": [2, 5, 6, 8, 12, 13, 14, 16], "inputs": [12, 16], "insegur": 10, "insensitiv": 12, "inser": [3, 8], "insert": [5, 7, 8, 12, 13, 14, 16], "inserted": 8, "inserting": 12, "inserts": 12, "inser\u00e7\u00f5": 6, "insid": [1, 2, 4, 5, 6, 8, 10, 12, 14, 15, 16], "insir": 6, "insist": 10, "inspector": 8, "inspired": 7, "instal": [3, 6, 13, 17], "install": [2, 6, 13, 16], "installation": [2, 6], "installations": 17, "installed": [0, 1, 2, 3, 4, 5, 6], "installs": 2, "instanc": [4, 5, 6, 10, 12, 13, 14, 16], "instanci": 6, "instantiat": [6, 13, 15], "instantiated": 6, "instantiation": [13, 14], "instead": [1, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "instructions": [2, 12], "instru\u00e7\u00e3": 6, "instru\u00e7\u00f5": 6, "inst\u00e2nc": [6, 14], "int": [4, 6, 12, 13, 15], "int2ip": 6, "integ": [2, 6, 7, 10, 12, 16], "integers": 12, "integr": 0, "integrated": 1, "integration": 16, "integrity": [8, 16], "inteir": [4, 6], "intended": [6, 16], "intentionally": 12, "interaction": 5, "interag": 0, "interchangeably": 13, "interfac": [0, 3, 5, 6, 14, 15, 16], "interior": 8, "intermedi\u00e1r": 6, "intern": [5, 6], "internacionaliz": [0, 4, 17], "internal": [0, 1, 8, 13], "internally": [4, 5, 6, 14], "internationaliz": 12, "internationalization": [5, 12, 15], "internationalized": 12, "interpret": [0, 6], "interpreted": 10, "interpreting": 7, "interromp": 6, "intersec\u00e7\u00e3": 6, "into": [0, 2, 4, 5, 6, 8, 10, 12, 13, 15, 16], "introdu": 2, "introduc": 0, "introduction": [1, 17], "introduz": 8, "intuitively": 12, "invalid": [2, 6, 12, 13], "invalidated": 5, "invalidating": 12, "invers": 6, "invert": [6, 12], "invisibl": 6, "involv": [6, 14, 15], "involved": [6, 8], "inv\u00e9s": 6, "in\u00edci": 6, "in\u00fatil": 6, "io": [2, 6], "ip": [2, 6, 12], "ip2int": 6, "ip_list": 13, "ipaddr": 6, "ipaddress": 13, "iptabl": 2, "ipv4": [6, 12], "ipv4address": 13, "ipv4network": 13, "ipv6": 12, "irem": [4, 5], "ir\u00e1": [2, 3, 4, 6, 8, 13, 14], "is": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "is_6to4": 12, "is_activ": 6, "is_automatic": 12, "is_dat": 6, "is_datetim": 6, "is_decimal_in_rang": 6, "is_empty_or": 6, "is_float_in_rang": 6, "is_impersonating": 13, "is_in_db": [6, 14], "is_in_set": 6, "is_int_in_rang": 6, "is_json": 6, "is_length": 6, "is_link_local": 12, "is_localhost": 12, "is_multicast": 12, "is_not_empty": 6, "is_null_or": 14, "is_privat": 12, "is_public": 6, "is_reserved": 12, "is_routeabl": 12, "is_tered": 12, "is_tim": 6, "isdir": [7, 14], "isn": [12, 14], "iso": 7, "isolation": 16, "issu": [0, 4, 6], "it": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "italian": [5, 11], "item": [6, 8, 10], "itemize1": 8, "itemize2": 8, "items": [6, 7, 8, 12], "itens": 6, "iter": 8, "iterabl": [6, 12], "iterations": 12, "iterators": 6, "iterselect": 6, "its": [0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "itself": [2, 6, 12, 16], "it\u00e1l": 10, "iv": 6, "janel": 2, "javascript": [1, 4, 7, 10, 16], "jdbc": 6, "jdbcpostgr": 6, "jdbcpostgresqladapt": 6, "jdbcsqlit": 6, "jdbcsqliteadapt": 6, "jetbrains": 1, "jim": [0, 1, 14], "jinja2": 5, "job": [7, 12, 14], "jog": [6, 11], "john": [0, 6], "join": [4, 5, 7, 12, 14, 15], "joined": 14, "joins": [14, 17], "jonathan": 6, "jorn": 1, "journalist": [7, 14], "jpeg": 12, "jpg": 12, "jpsteil": 14, "jquery": [10, 12, 16], "js": [0, 4, 5, 8, 11, 15, 17], "jsl": 16, "json": [0, 1, 2, 4, 5, 6, 7, 11, 12, 15, 16], "junt": [10, 14], "jun\u00e7\u00e3": [6, 13], "jun\u00e7\u00f5": 6, "just": [1, 2, 4, 5, 6, 7, 8, 12, 14, 16], "jwt": 5, "jython": 6, "k": [5, 8, 15], "kargs": 10, "kbd": 2, "kbytes": 5, "keep": [1, 2, 4, 5, 6, 7, 8, 10, 12, 15], "keep_valu": 12, "keeps": [5, 14], "kell": 0, "ken": 6, "kent": [7, 14], "kevin": 0, "key": [2, 4, 5, 6, 7, 10, 12, 13, 16, 17], "keycod": 16, "keyed": 6, "keys": [6, 7], "keyup": 16, "keyword": [6, 8, 10], "keywords": 6, "kfield": 16, "killed": 16, "kind": [4, 13], "kindness": 0, "kinds": 4, "kinterbasdb": 6, "know": [5, 14], "known": [6, 8], "known_expressions": 11, "knows": [4, 5], "ktabl": 16, "kwargs": [6, 16], "ky8iq0g4b3cyey6wyhn3yt9pw0xpsrivlkmxe40ptknxrlnz9": 8, "l": [2, 12], "la": [4, 6], "label": [6, 7, 8, 12, 16], "labeling": 15, "labels": 12, "lac": 6, "lacking": 12, "lacks": 15, "lad": [1, 5, 6], "lambd": [5, 6, 10, 12, 14, 16], "lang": 5, "languag": [1, 4, 5, 8, 11, 15, 16], "larg": [0, 12], "last": [1, 5, 8, 12, 13, 14, 16], "last_insert_id": 6, "last_nam": [5, 13, 14], "last_row": 6, "lastdot": 12, "lastrowid": 6, "lat": [1, 2, 4, 5, 6, 8, 10, 12, 14], "latest": [2, 6, 15], "latin1": 6, "latt": [6, 12, 15], "launch": [1, 2], "lax": 5, "lay": [5, 15, 17], "layers": 5, "layout": [12, 13, 14, 15, 16, 17], "layouts": [8, 14], "lazily": 16, "lazy": [2, 4, 6], "lazy_tabl": 6, "lazy_total_pric": 6, "ldap": [0, 5], "ldap_plugin": 13, "ldap_setting": 13, "ldap_settings": 13, "ldapplugin": 13, "lead": [14, 16], "leads": 5, "learn": [1, 6], "least": [1, 2, 12, 15, 16], "leav": [5, 6], "left": [8, 12, 14, 16], "legacy": 6, "leg\u00edvel": 6, "leitur": 6, "lembr": [3, 6, 8, 12], "len": [12, 16], "length": [6, 12], "ler": [1, 6], "less": [7, 12, 15, 16], "let": [7, 12, 16], "lets": 2, "letters": 12, "letting": 6, "lev": [6, 10], "levant": 6, "level": [2, 4, 6, 7, 12, 13], "leverag": 0, "li": [8, 12, 13, 16], "lib": 2, "libldap2": 13, "librari": 13, "library": [2, 6, 11, 12, 15, 16], "libs": [8, 10, 14], "libsasl2": 13, "libsass": 4, "licens": 1, "lid": [1, 4, 5, 6, 14], "lif": 16, "lifespan": 12, "lifetim": 5, "lig": [2, 5, 6, 10], "liga\u00e7\u00e3": [4, 5, 6], "liga\u00e7\u00f5": [6, 10], "light": [6, 15], "lik": [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16], "limit": [5, 6, 7, 12, 13], "limitation": 8, "limited": [0, 2, 5, 15, 16], "limiting": 16, "limits": 12, "limp": 1, "lin": [2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 16], "linguag": [0, 4, 5, 17], "linguagens": 4, "linh": [5, 6, 8, 10, 14, 17], "link": [4, 7, 8, 10, 12, 13, 14], "linked": 7, "links": [2, 6, 14, 16], "linting": 1, "linux": 2, "list": [0, 1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 16], "list_of_fields": 6, "listabl": 6, "listagens": 10, "listed": [5, 12, 16], "listen": [2, 4], "listening": [2, 3], "listproperty": 6, "lists": [6, 14], "liststringproperty": 6, "listwidget": 12, "littl": [1, 5, 6], "liv": [6, 16], "livr": [1, 6], "lix": 6, "ll": [1, 2, 3, 5, 6, 8, 12, 14, 16], "lo": [1, 2, 3, 4, 6, 7, 8, 10], "load": [3, 5, 16], "loaded": [4, 8, 14, 16], "loading": 16, "loads": [6, 16], "loazkjy": 8, "loc": 5, "local": [3, 5, 8, 12, 13, 14], "localhost": [2, 4, 5, 6, 12], "localiz": 4, "locally": 2, "locals": [5, 12, 14, 15], "locat": 15, "location": [2, 4, 6, 8, 15], "locked": 6, "locking": 5, "locks": 16, "log": [0, 2, 4, 5, 6, 8, 10, 13, 15, 16], "logerrors": 5, "logfil": 6, "logged": [5, 8, 13, 15], "logging": [2, 13], "logging_level": 2, "logic": [2, 5, 6, 13, 14, 15, 16], "logical": 12, "login": [0, 2, 3, 4, 5, 6, 8, 12, 13, 15], "logout": [0, 8, 13], "logs": [2, 5, 6], "loj": 6, "long": [0, 2, 6, 12, 16], "longhash_tablenam": 6, "longtext": 6, "look": [4, 5, 6, 7, 12, 13], "looking": 3, "looks": [2, 6, 12], "lookup": [5, 7], "lookups": 12, "loop": [6, 8, 16], "looping": 6, "loops": 16, "los": [2, 4, 6, 12], "lost": [5, 15], "lot": [4, 6], "lots": 1, "loved": 0, "low": [0, 12], "lowercas": 12, "lowest": 12, "lru": 5, "lt": 10, "lts": 2, "luc": [0, 1], "lug": [1, 5, 6], "lumin\u00e1r": 4, "l\u00e1": [2, 4], "l\u00edngu": 1, "m": [0, 2, 6, 10, 12], "mac": [2, 6], "macac": 5, "machin": 2, "macneiln": 2, "mad": [8, 12, 14], "magically": 0, "mai": 5, "mail": [2, 3, 5, 12, 13, 16], "mailing": 6, "mailt": 12, "main": [1, 2, 3, 4, 6, 8, 14, 15, 16], "maintain": [6, 8, 16], "maintainability": 6, "maintainabl": 14, "maintenanc": 16, "maior": [6, 7], "mai\u00fascul": 6, "major": 6, "mak": [0, 1, 2, 5, 6, 8, 10, 12, 13, 14, 15, 16], "makefil": 2, "making": [6, 8], "man": 16, "manag": [2, 3, 4, 5, 6, 13, 14, 16], "managed": 0, "management": 12, "managing": 6, "mandatory": [5, 8], "maneir": [6, 14], "manipul": 6, "manipulat": 12, "manipulated": 12, "manipulation": 4, "mann": [5, 15], "manual": [2, 3, 4, 6], "manually": [2, 4, 6, 10, 13, 14], "many": [0, 1, 2, 4, 5, 7, 8, 12, 14, 15, 16], "map": [4, 6, 10, 11], "map_non": 6, "mapped": 17, "mapping": [2, 4, 6, 12, 15], "maps": [6, 10], "marc": [1, 6, 10, 13], "marca\u00e7\u00e3": [10, 13], "margin": [8, 16], "marked": 6, "massim": [0, 6], "mast": [1, 2, 14], "match": [2, 5, 6, 8, 10, 11, 12, 16], "matched": [4, 7, 10, 12], "matching": [7, 10, 12], "matem\u00e1t": 6, "material": 6, "math": 16, "matriz": 10, "matters": 15, "max": [11, 12, 16], "max_concurrent_runs": 16, "maximum": [5, 12], "maxip": 12, "maxlen": 12, "maxsiz": 12, "may": [2, 4, 5, 6, 8, 10, 12, 13, 15, 16], "md": 1, "md5": 12, "mean": 15, "meaning": [6, 12, 13], "meanings": 12, "means": [1, 2, 3, 5, 6, 8, 12, 13, 15], "mecan": [6, 8], "mechanism": [0, 4, 5, 6, 8, 10, 13, 15, 16], "med": 1, "mediant": 8, "mei": 6, "melhor": [0, 1, 6, 11, 14, 17], "memb": [12, 13], "membership": [0, 5, 12, 13, 15], "memberships": 5, "membr": 13, "memcach": [0, 6], "memoiz": 17, "memory": [5, 6], "men": [1, 2, 5, 6], "menor": 6, "mensag": 14, "mensagens": [5, 6], "ment": 1, "mention": 5, "mentioned": [13, 16], "menu": [8, 12, 13], "menus": [8, 12], "mes": [6, 10], "mescl": 6, "mesm": [0, 1, 2, 4, 5, 8, 10, 11, 14], "messag": [2, 4, 5, 7, 8, 12, 14, 15, 17], "messed": 1, "mestr": 6, "met": [6, 8, 10], "metad": 6, "metadat": 6, "metatag": 10, "method": [4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "methods": [5, 7, 10, 12, 13, 14, 17], "mfa": 13, "micah": 0, "microsoft": [1, 13], "mid": 6, "middlewar": 5, "might": [4, 7, 12], "migr": 6, "migrate_enabled": 6, "migrated": 6, "migrating": [6, 15], "migration": 6, "migrations": 6, "migra\u00e7\u00f5": 17, "min": [8, 12, 14], "min_length": 12, "mind": [4, 6, 7, 8, 12], "mindful": 6, "minimal": [4, 5, 14, 17], "minimalist": [8, 15], "minimalist_pag": 8, "minimum": 12, "minip": 12, "minor": [8, 12, 15], "minsiz": 12, "minut": 12, "min\u00fascul": 6, "missing": [2, 13, 14], "mistak": 2, "mix": 8, "mkdir": [2, 4, 7, 14], "mm": 12, "mobili\u00e1ri": 5, "mod": [2, 4, 5, 6, 12, 13], "model": [0, 3, 7, 10, 12, 15, 16], "models": [4, 6, 7, 14, 15], "modern": [6, 13, 16], "modifi": 14, "modific": [2, 4], "modification": 12, "modifications": 2, "modified": [4, 12, 15], "modified_by": 6, "modified_on": 6, "modifiers": 7, "modify": [12, 14, 16], "modifying": [2, 6, 8], "modindex": 17, "modul": [0, 1, 2, 4, 6, 8, 10, 12, 13, 14, 15], "moment": [2, 6], "mong": 6, "mongodb": 6, "mongodbadapt": 6, "monoespac": 10, "monolithic": 0, "mont": 13, "month": 12, "mor": [0, 2, 4, 5, 6, 7, 8, 10, 12, 14, 15, 16], "moreov": 16, "most": [0, 4, 5, 6, 12, 13, 14, 15, 16], "mostr": [1, 4, 6, 14], "moth": 6, "mother_id": 6, "motor": 6, "mov": 6, "mssql1": 6, "mssql1n": 6, "mssql2": 6, "mssql2adapt": 6, "mssql3": 6, "mssql3adapt": 6, "mssql3n": 6, "mssql4": 6, "mssql4adapt": 6, "mssql4n": 6, "mssqladapt": 6, "mssqln": 6, "mtabl": 0, "much": [0, 1, 5, 6, 8, 12, 14, 15, 16], "mud": [4, 5, 6], "mudanc": 6, "muit": [0, 1, 4, 5], "mult": [1, 2, 6, 15], "multicast": 12, "multipl": [1, 2, 4, 6, 8, 10, 12, 15, 16, 17], "multiprocess": 5, "multiselect": 12, "multius": 13, "must": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 15, 16], "my": [5, 8, 10, 12, 13, 16], "my_app": 4, "my_id": 16, "my_password_fil": 2, "my_task": 16, "my_url_path": 15, "my_var": [5, 10], "myapp": [2, 4], "myclass": 10, "mycomponent": 16, "mycustomwidget": 12, "mydb": 6, "myerrors": 5, "myfield": 6, "myfil": 6, "myfixtur": 5, "myfunction": 2, "myidx": 6, "myobj": 6, "myobjnam": 6, "myord": 6, "myquery": 6, "myrecord": 6, "mysaltvalu": 12, "mysendgridsend": 16, "myset": 6, "mysideb": 8, "mysqladapt": 6, "mysqldb": 6, "mysqldv": 6, "mystyle": 12, "mytabl": 6, "myvalu": 6, "myvirtualfields": 6, "myvirtualfields1": 6, "myvirtualfields2": 6, "m\u00e1quin": [6, 10], "m\u00e1x": 6, "m\u00e1xim": 6, "m\u00e9d": 6, "m\u00e9di": 13, "m\u00e9tod": [4, 5, 6, 8, 14], "m\u00ednim": [5, 6], "m\u00f3dul": [0, 2, 5, 6], "m\u00faltipl": [0, 6], "n": [5, 6, 11, 16], "nad": [2, 4, 6], "nam": [2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "named": 10, "nameonly": 6, "naming": 6, "nasc": 6, "natal": 6, "nativ": 6, "native_json": 12, "nav": 8, "navb": [8, 13], "naveg": 14, "navigat": 16, "navigation": [8, 16], "ndb": 6, "ne6fz": 8, "necess": [1, 2, 4, 6, 8], "necessary": [6, 8, 10], "necessit": 6, "necess\u00e1r": [1, 6], "necess\u00e1ri": [2, 4, 6], "need": [0, 1, 2, 5, 6, 8, 10, 12, 13, 14, 15, 16], "needed": [2, 4, 5, 6, 10, 12, 13, 16], "needs": [0, 4, 5, 6, 13, 15, 16], "neg": 6, "negated": 6, "negativ": [6, 12], "nega\u00e7\u00e3": 6, "neith": 12, "nel": 6, "nenhum": [6, 13], "ness": 6, "nest": [1, 6, 8, 10], "nested": [6, 8], "nested_select": 6, "network": [12, 13], "networks": [6, 12, 13], "nev": [6, 8, 12, 15, 16], "new": [0, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "new_app": [8, 10], "new_password": 16, "new_sidec": 16, "newlin": [6, 12], "newly": 6, "next": [1, 5, 6, 8, 12, 16], "nginx": [2, 4], "nic": [0, 16], "nicozanf": 2, "nid": 6, "niss": 6, "no_backslash_escap": 6, "no_tabl": [12, 16], "nod": [4, 16], "nom": [2, 4, 5, 7, 10, 12, 13, 14], "nomeaplic": 13, "non": [2, 4, 5, 6, 7, 10, 12, 13, 14, 15, 16], "nor": [2, 12], "norm": 6, "normal": [4, 5, 6, 8, 12, 14, 16], "normaliz": 6, "normalized": 6, "normally": [2, 6, 8, 10, 12, 14], "northwind": 1, "nosqladapt": 6, "noss": [1, 4, 5, 6], "not": [0, 1, 2, 4, 5, 7, 8, 10, 12, 13, 14, 15, 16], "not_authorized": 13, "notation": 10, "nota\u00e7\u00e3": [6, 10], "noted": 8, "nothing": [2, 5, 6, 12, 16], "notic": [2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "notnull": 6, "notset": 2, "nov": [0, 1, 2, 3, 4, 14], "novaaplicaca": 4, "now": [0, 4, 5, 6, 12, 14, 16], "nowadays": 1, "nul": 6, "null": [6, 7, 12, 16], "numb": [2, 5, 6, 8, 12, 13, 16], "number_workers": 2, "numbers": 12, "numerical": 12, "num\u00e9r": 6, "nunc": [5, 6], "n\u00e3": [0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 14], "n\u00edvel": [6, 11], "n\u00famer": [4, 6, 11, 14], "oauth": 13, "oauth2": [0, 5], "oauth2discord": 13, "oauth2facebook": 13, "oauth2googl": 13, "obj": [6, 8], "object": [0, 4, 5, 6, 7, 8, 10, 12, 13, 15, 17], "objects": [0, 4, 5, 6, 10, 12, 14, 15, 16], "objet": [0, 5, 6, 8, 10, 11], "obras": 6, "obrigat\u00f3ri": 6, "observ": [4, 6, 8], "obsolet": 6, "obtain": [2, 13, 16], "obtained": 6, "obter": [4, 6], "obtid": [2, 6], "obvi": 6, "obvious": [7, 8, 13], "obviously": 12, "ocasional": 6, "occasionally": 12, "occur": [2, 12, 16], "occurring": 2, "occurs": 12, "ocorr": [4, 6, 8], "ocult": [6, 12], "odd": [6, 8], "of": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "off": [0, 2, 4, 14, 16], "official": [0, 12, 16], "offs": 6, "offset": [6, 7], "oficial": 6, "often": [4, 6, 13, 16], "ok": [10, 15], "old": [2, 5, 12, 15], "older": 12, "olhand": 6, "ol\u00e1": 8, "ombott": [0, 4, 15], "omit": 2, "omitted": 2, "on": [0, 1, 3, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "on_delete_action": 6, "on_error": 5, "on_fals": 5, "on_request": [5, 13], "on_success": 5, "once": [1, 2, 5, 6, 12, 13, 16], "onclick": 16, "onde": [2, 6, 7, 8, 12], "ondelet": 6, "one": [0, 2, 4, 5, 6, 7, 8, 12, 13, 14, 16], "ones": [2, 5, 7, 12, 15], "onion": 5, "onkeydown": 16, "onlin": [1, 6], "onload": 16, "only": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "onvalidation": 12, "opacity": 16, "opcion": [6, 10], "opcional": [4, 6, 10, 12], "open": [1, 2, 4, 5, 6, 10], "opening": 5, "oper": [0, 3, 11, 17], "operat": 12, "operation": [6, 13], "operationalerror": 12, "operations": [5, 6], "operator": [5, 6, 12], "oposi\u00e7\u00e3": 10, "opost": 6, "opposed": 5, "opposit": 5, "oprow": 6, "optimized": 6, "option": [2, 3, 5, 6, 12, 13, 16], "optional": [2, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "optionally": [0, 2], "options": [2, 6, 7, 10, 13, 15, 16], "opt\u00e1m": 6, "op\u00e7\u00e3": [3, 4, 6, 8, 10], "op\u00e7\u00f5": [6, 17], "or": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "oracl": 6, "oracleadapt": 6, "ordem": 6, "order": [1, 2, 4, 5, 6, 7, 10, 12, 14, 15], "order_it": 6, "orderby": [12, 13, 14, 16], "ordered": 10, "ordereddict": 6, "org": [2, 4, 7, 10, 16], "organiz": 6, "organized": 4, "orig": [2, 11], "origin": 6, "original": [6, 8, 10, 12], "orm": 6, "other": [0, 1, 2, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16], "other_pag": [12, 16], "otherfield": 6, "others": [0, 6, 15], "othertabl": 6, "otherwis": [4, 5, 6, 8, 12, 13, 14], "otimiz": 6, "oufil": 6, "our": [0, 1, 2, 5, 6, 10, 12, 16], "out": [0, 1, 2, 5, 8, 12, 16], "outlined": [4, 5], "output": [1, 2, 5, 8, 12, 16], "output_styl": 4, "outr": [1, 2, 4, 5, 10, 11, 13, 14, 17], "outsid": [0, 5, 6, 12, 14, 15, 16], "ov": 6, "over": [8, 14, 16], "overkill": 13, "overrid": [4, 6, 8, 13, 14, 15, 16], "override_cl": 14, "override_styl": 14, "overriding": 16, "overview": 17, "overwritten": [5, 15], "own": [4, 5, 6, 8, 12, 13, 14, 15, 16], "owner": [6, 12], "owner_id": 6, "owner_id1": 6, "owner_id2": 6, "owners": 12, "ownership": 6, "owns": 6, "p": [2, 8, 12], "p10n": 11, "p11n": 5, "packag": [0, 6, 10], "padded": [5, 8, 15], "padding": 16, "padroniz": 6, "padr\u00e3": [0, 2, 4, 5, 8, 10, 14], "padr\u00f5": [4, 6, 10], "pag": [1, 3, 4, 5, 6, 10, 12, 13, 14, 15, 16, 17], "page_head": 8, "page_left_menu": 8, "page_scripts": 8, "pagin": [6, 14], "pai": 6, "painel": [0, 2, 3], "paint": [4, 12], "painting": 4, "pairs": 10, "palavr": [8, 10, 11], "pam": [0, 5], "pam_plugin": 13, "pamplugin": 13, "papel": 2, "par": [0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 16, 17], "paragraph": [5, 10, 12, 14], "paramet": [1, 2, 4, 5, 6, 13, 14, 16], "parameters": [2, 6], "params": 16, "parec": [5, 6], "parent": [2, 6, 8, 16], "park": [7, 14], "pars": [5, 6, 12, 13], "parsed": 6, "parsemodul": 4, "parsing": 4, "part": [4, 10, 15], "partial": [4, 6, 15], "particip": 6, "participat": 1, "particul": [0, 1, 6, 12, 14, 15, 16], "particular": 6, "particularly": 6, "parts": 12, "party": [0, 5], "par\u00e1graf": [1, 10], "par\u00e2metr": [5, 14], "par\u00eantes": 6, "pass": [4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "passed": [2, 4, 5, 6, 10, 12, 13, 14, 16], "passing": [5, 6, 10, 12], "passphras": 5, "password": [2, 6, 8, 12, 13, 15, 16], "password_fil": 2, "passwords": [12, 13], "passwordwidget": 12, "past": [2, 4], "path": [2, 4, 5, 6, 7, 11, 12, 14, 15, 16], "path_t": 2, "pattern": 7, "patterns": 7, "paus": 2, "pay": 4, "payment": 6, "payroll": 5, "pbkdf2": 12, "pc": 3, "pdf": [1, 12], "pdkdf2": 2, "pec": [5, 6], "ped": [2, 4, 5, 6, 14], "pedac": 8, "peg": 6, "pegadinh": 17, "pel": [1, 4, 5, 6, 8, 10, 11, 13], "pens": 6, "per": [5, 7, 14], "percentual": 6, "perd": [1, 2, 6], "perfect": 0, "perfectly": 14, "perfil": [0, 13], "perform": [5, 6, 12, 13], "performanc": [0, 5, 6, 15], "performed": 13, "performing": 6, "performs": 12, "pergunt": 6, "perhaps": 5, "period": 16, "periodic": 16, "permanec": 6, "permission": [5, 13], "permissions": [0, 5, 6, 13, 15], "permiss\u00e3": [5, 6, 13], "permit": [0, 3, 5, 6, 8, 10, 13, 14], "permitted_tags": 10, "persist": 5, "persistent": [6, 13], "person": [6, 7, 12, 14], "personag": 6, "personagens": [4, 6], "personal": 2, "personaliz": [0, 2, 17], "persons": 12, "persons_and_things": 6, "perspectiv": 0, "pertenc": 6, "pertencent": 6, "pesquis": [2, 3, 6, 14], "pesso": 6, "pet": [6, 7, 14], "philip": 6, "phon": [12, 16], "photograph": [7, 14], "physics": 13, "pick": [5, 13], "picked": 4, "picks": 5, "piec": [0, 12], "pierr": 0, "pip": [0, 1, 6], "pirsch": 0, "piscin": [4, 6], "piu": 5, "pixels": 12, "plac": [1, 5, 6, 8, 12, 14, 16], "placehold": [12, 16], "placeholders": 6, "placing": 14, "plain": 16, "plan": [1, 16], "plataform": 17, "platform": [0, 1], "play": 16, "playing": 0, "pleas": [5, 6], "plug": 5, "plugin": [5, 12, 13, 14, 16], "plugins": [12, 16], "plural": 11, "pluraliz": [0, 4, 5, 14, 16, 17], "pluralization": 15, "plus": [6, 8, 13, 14], "pm": 12, "png": [10, 12], "pod": [0, 1, 2, 4, 5, 6, 7, 8, 10, 11, 13, 14], "point": [4, 5, 6, 12, 15, 16], "pointing": [2, 3, 8], "points": [6, 7, 12, 14], "polic": 17, "policy": 7, "pollut": 5, "pol\u00edt": 0, "pont": 6, "pool": 5, "pool_connection": 6, "pool_siz": [5, 6], "pooling": 6, "poor": 16, "pop": 16, "popul": [0, 6], "porqu": [2, 4, 5, 6, 8, 11], "port": [0, 2, 3, 5, 6, 15], "portability": 6, "portabl": 6, "portant": [2, 4, 5, 6, 10], "ports": 2, "por\u00e7\u00f5": 14, "posicion": 10, "position": 5, "positional": 10, "posi\u00e7\u00e3": [5, 10], "possibil": 6, "possibl": [4, 5, 6, 8, 12, 13, 14, 15, 16], "possibly": 6, "poss\u00edv": 6, "poss\u00edvel": [4, 6], "post": [4, 6, 7, 10, 12, 13, 14, 15, 16], "post_action_buttons": 14, "post_text": 6, "post_vars": [7, 15], "post_writabl": 7, "posted": 7, "postel": 11, "posterior": 5, "postfix": 15, "postgr": 6, "postgreboolean": 6, "postgrenew": 6, "postgrepsyc": 6, "postgrepsycoboolean": 6, "postgrepsyconew": 6, "postgres2": 6, "postgres3": 6, "postgres_nonreserved": 6, "postgresql": [2, 6], "postgresqladapt": 6, "postprocessing": 15, "posts": 6, "potentially": 2, "pouc": [5, 6], "pow": [2, 6, 16], "powerful": [7, 13, 15], "powers": 7, "pprint": 6, "pr": 1, "practic": [8, 12], "practical": [6, 12, 15, 17], "pre": [2, 4, 5, 14], "pre_action_buttons": 14, "precau\u00e7\u00e3": 2, "preced": [5, 6, 8], "preceded": [6, 12], "precedent": 6, "preceding": 7, "precious": 14, "precis": [1, 2, 4, 5, 6, 14], "predefin": 8, "predefined": [12, 16], "predetermined": 15, "preench": 6, "preenchiment": 6, "pref": [2, 16], "prefer": 10, "preferenc": 5, "preferred": [5, 6], "prefix": [2, 4, 6, 7, 8, 12, 15], "preguic": 15, "prelimin": 1, "preocup": 6, "prepend": [12, 16], "prepend_schem": 12, "prepended": [12, 15], "prepending": 12, "prepends": 4, "preprocessing": 15, "prerequisit": [2, 5], "presenc": [5, 6], "present": [2, 6, 8, 12, 13], "preserv": [0, 5], "preserved": 5, "press": [4, 14], "pression": 3, "prest": 10, "pretend": 6, "pretty": [6, 15], "prevent": [6, 10, 12, 16], "prevented": 6, "preventing": 5, "prevents": [2, 5, 16], "previ": [6, 12], "previous": [5, 8, 10, 12, 16], "previously": [5, 6, 12], "prim": [5, 12], "primarily": 14, "primary": [6, 8, 13], "primeir": [6, 8, 13, 17], "princip": 0, "principal": [1, 5, 6, 17], "princ\u00edpi": [6, 17], "print": [4, 6, 10, 11, 13, 14, 15, 16], "printed": 4, "privat": [5, 12], "probability": 12, "probl": [0, 6, 8, 14], "problem": [1, 6], "problems": [6, 13, 16], "proc": [13, 16], "proced": 17, "procedur": 2, "process": [2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "processed": [5, 6, 8, 12], "processing": [5, 6, 8, 12, 16], "procur": [1, 6], "produc": [6, 10, 12, 13], "product": [6, 16], "product_record": 16, "production": [2, 4], "products": 6, "produz": [2, 5, 6, 8, 10], "produ\u00e7\u00e3": 0, "professor": 13, "profil": [8, 13, 15], "progr": [1, 2, 3, 10], "program": [1, 3, 6, 8], "programmatically": 10, "programming": [1, 6, 8, 16], "programs": [1, 2, 12], "project": [0, 2, 4, 5, 6], "project_nam": 2, "projet": [0, 2], "prompt": [2, 4, 6], "pront": 6, "prop": [5, 6, 14], "properly": 4, "properti": [6, 12], "propriedad": 6, "propriet\u00e1r": 6, "propriet\u00e1ri": 6, "protocol": 3, "prototyp": 16, "provavel": [4, 5, 6], "proveit": 6, "provid": [0, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "provided": [5, 6, 8, 10, 12, 14], "providing": [0, 14, 15], "provoc": 6, "proxi": 4, "proxy": 4, "proxy_http_version": 4, "proxy_pass": 4, "proxy_set_head": 4, "prudent": 6, "pr\u00e1tic": [0, 6, 7], "pr\u00e9": [10, 14, 17], "pr\u00f3pr": [13, 14], "pr\u00f3pri": [6, 14], "pr\u00f3xim": [2, 6], "pseud": 6, "psycopg2": 6, "public": [4, 6], "pud": 2, "pull": 1, "punycod": 12, "pur": 6, "purpos": [0, 6, 10, 12, 13, 15], "put": [5, 7, 12, 16], "put_writabl": 7, "putting": 4, "pux": 6, "pwd": 6, "py": [1, 2, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16], "py4web": [2, 3, 4, 5, 7, 8, 10, 12, 13, 14], "py4web_filesyst": 6, "py4web_wsg": 2, "pyc": 8, "pydal": [0, 2, 3, 5, 6, 7, 12, 13, 14, 15, 16], "pyfilesyst": 6, "pyinstall": 2, "pymong": 6, "pymysql": 6, "pyodbc": 6, "pypi": 2, "pypyodbc": 6, "pysqlite2": 6, "pytds": 6, "python": [0, 2, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 16], "python2": [2, 15], "python3": [1, 2], "pyweb": 15, "p\u00e1gin": [8, 10, 14, 17], "p\u00f3s": 14, "p\u00fablic": 6, "q": [2, 5, 6, 14], "qua": [0, 6], "quadr": [0, 1, 6], "quaisqu": [6, 12], "qualified": 6, "qualqu": [1, 2, 4, 5, 6, 7, 8, 13], "quand": [1, 4, 6, 8, 14], "quant": 6, "quantity": 6, "quebr": 0, "qued": 14, "queir": 5, "quer": [2, 4, 5, 6, 10], "queri": [6, 7, 14, 15, 16], "queried": 7, "query": [4, 5, 7, 10, 12, 14, 15, 16], "query1": 6, "query2": 6, "queryselector": 16, "queryselectorall": 16, "querystring": 14, "questions": [1, 2], "quest\u00e3": 6, "quick": [1, 12], "quickly": [2, 12, 14], "quickstart": 16, "quiet": 2, "quirk": 5, "quis": [4, 6], "quit": [1, 2, 5, 7, 8, 12, 14], "quot": [2, 10], "quote_minimal": 6, "quote_nonnumeric": 6, "quotech": 6, "r": [2, 6, 12], "rac": 12, "radi": [10, 12], "radiowidget": 12, "radius": 16, "rais": [5, 12, 13, 15], "raised": 5, "ram": [5, 6], "randint": [8, 13], "random": [6, 8, 12, 13, 16], "rang": [4, 6, 8, 13, 15], "rapid": [0, 16], "rar": 6, "rarely": 6, "rath": [6, 8, 12, 16], "raw": 17, "raz\u00f5": 6, "rb": 6, "re": [0, 1, 2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "rea": 6, "reach": 2, "reached": 5, "react": 16, "reactivity": 16, "read": [1, 4, 5, 6, 7, 14, 16], "readability": 7, "readabl": [5, 6, 14, 15], "readm": [1, 12], "readonly": [2, 12, 16], "ready": 2, "reagrup": 0, "real": [2, 4, 7, 8, 12], "real_identity": [6, 7], "realiz": [3, 4, 6], "really": [1, 4], "realment": 6, "reaping": 16, "reason": [2, 5, 6, 13, 14, 15], "reasons": [2, 7], "rebuilt": 6, "rec_id": 7, "receb": [6, 8], "recent": [2, 3, 6, 12, 14], "recently": 5, "recereived": 16, "recip": 2, "reclam": 6, "recogniz": 4, "recognized": 6, "recolh": [7, 14], "recomec": 6, "recomend": 6, "recommend": [1, 5, 16], "recommended": [4, 6, 12], "reconstru": 6, "record": [7, 12, 13, 15, 16], "record_id": [7, 16], "recorded": 12, "records": [6, 7, 12, 13, 14, 16], "recorrent": 6, "recovered": 12, "recreat": 6, "recreated": 6, "recup": 6, "recuper": [4, 6], "recurs": [2, 8, 17], "recursively": 8, "recycl": 6, "rec\u00e9m": [4, 6, 11], "red": [0, 3, 4, 6, 8, 10, 12, 17], "redefin": 5, "redefini\u00e7\u00e3": 6, "redesign": 0, "redirecion": [4, 13, 14], "redirect": [4, 5, 12, 13, 14, 16], "redirected": [5, 16], "redirection": [5, 12, 15], "redirects": [5, 13], "reduc": [0, 5, 16], "reduced": 0, "redundant": 6, "reescrev": 6, "reescrit": 6, "ref": [3, 5, 6, 10, 12, 13, 17], "refer": [6, 7], "referenc": [1, 5, 6, 7, 8, 10, 12, 14, 16], "referenced": [6, 7], "referenced_by": 7, "referencing": 6, "referim": 6, "referred": [6, 7], "refers": 7, "reflected": 1, "reflet": 6, "reforc": 6, "refresh": 14, "regex": [7, 10, 12], "regexlib": 12, "regist": [0, 5, 6, 8, 13, 14, 15, 16], "register_plugin": 13, "register_task": 16, "register_vue_component": 16, "registered": [5, 13], "registers": 5, "registr": [3, 4, 12, 13, 14], "registration": [4, 12], "registration_stamp": 12, "regr": [4, 5], "regul": [0, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16], "reimplementation": 16, "reinic": 6, "reinstal": 2, "reinstall": 2, "reinstat": 6, "rejected": [12, 13], "rejects": 12, "rel": [8, 14], "relacion": 6, "relat": 6, "related": [5, 6], "relational": 6, "relations": 17, "relationships": 6, "relativ": [4, 15], "rela\u00e7\u00e3": 10, "rela\u00e7\u00f5": 6, "releas": 2, "released": 0, "relev": 6, "reload": [2, 3, 4, 5, 12], "reloaded": [3, 4, 16], "reloading": [2, 4, 5], "reloads": [4, 5], "rely": [0, 4, 6], "rem": 5, "remain": [6, 12], "remains": 0, "rememb": [5, 6], "remembered": 5, "remote_addr": [5, 13], "remov": [2, 4, 6, 10, 12, 13], "removal": 12, "removed": [0, 6, 10, 12], "removing": 6, "remo\u00e7\u00e3": 6, "renam": 1, "rend": [5, 6, 12, 14, 15, 16], "rendered": [5, 7, 8, 12, 14, 15, 16], "rendering": [8, 10], "renderiz": 10, "renders": 12, "reno": 8, "renom": 7, "reopening": 5, "repackaging": 0, "repeated": 12, "repeti\u00e7\u00e3": 6, "replac": [6, 8, 10, 14, 16], "replaced": [8, 10, 12, 16], "replacing": 12, "replicat": [1, 5], "report": 8, "reported": 4, "repository": [1, 2, 3], "reposit\u00f3ri": 2, "repr_row": 6, "represent": [8, 10, 12], "representation": [6, 10, 16], "representational_state_transf": 7, "represented": 12, "representing_field": 12, "represents": [0, 6], "requ": [0, 5, 6, 12, 13, 14, 15, 16], "request": [0, 1, 2, 5, 6, 7, 12, 13, 14, 15, 16], "request_body": 16, "request_reset_password": 13, "request_ur": 4, "requests": [1, 4, 5, 15], "requir": [2, 5, 6, 12, 13], "required": [4, 5, 6, 7, 12, 16], "requirement": [12, 16], "requirements": [0, 2, 12], "requires_": 15, "requires_login": 15, "requires_membership": 13, "requiring": [2, 15], "requisit": [6, 17], "res": 16, "reserv": 11, "reserved": [6, 12], "reset": 6, "reset_password": 13, "resgat": 6, "resourc": [6, 13], "respect": [6, 12, 14], "respectively": [6, 12], "respons": [4, 5, 6, 8, 15, 16, 17], "responsibility": 6, "responsibl": [14, 16], "respost": [5, 8], "ressalv": 6, "restabelec": 6, "restap": [0, 3, 17], "restart": [2, 4, 5, 6, 14], "restarting": 6, "restaur": 6, "restful": [7, 13], "restrict": [5, 7, 12, 16], "restri\u00e7\u00e3": 6, "restri\u00e7\u00f5": [0, 6], "restructuredtext": 1, "result": [1, 4, 6, 7, 8, 12, 13, 14, 16], "resulting": [8, 12, 16], "results": [6, 8, 12, 14], "ret": 6, "retain": 6, "retorn": [5, 8, 10], "retribu": 10, "retriev": [5, 6, 16], "retrieval": 6, "retrieved": 15, "return": [4, 5, 6, 7, 10, 12, 13, 14, 16], "returned": [5, 6, 7, 8, 10, 12, 13, 16], "returning": 5, "returns": [5, 6, 7, 10, 12, 13, 15, 16], "reutiliz": [2, 6], "revers": [4, 5, 6], "revers\u00e3": 4, "revert": 6, "rfc": 12, "rid": 6, "riding": 8, "right": [5, 6, 8], "rights": 2, "road": 16, "robust": 16, "rocket": 15, "rocket3": [2, 15], "rocketserv": 2, "rodap": 10, "rol": [5, 10], "roll": 6, "rollback": 16, "rolls": 5, "root": [6, 8, 13, 14], "rosc": 5, "rot": [3, 14], "rotul": 6, "rout": [2, 5, 14, 15, 16], "routing": [0, 2, 4, 15], "rov": 6, "row": [12, 14, 16], "rows": [10, 12, 14, 15], "rows1": 6, "rows2": 6, "rows3": 6, "rows_list": 6, "rows_per_pag": 14, "rpc": 6, "rst": 1, "rul": [8, 12, 14], "run": [1, 3, 4, 6, 8, 13, 16], "run_in_transaction": 6, "running": [1, 2, 4, 5, 16], "runs": [2, 4, 13, 15, 16], "r\u00e1di": 10, "r\u00e1p": [0, 6], "r\u00f3tul": [6, 10, 14], "s": [0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 13, 14, 16], "s3": 6, "s_": [12, 16], "s_autocomplet": 16, "s_autocomplete_results": 16, "s_down_key": 16, "s_search": 16, "sab": [1, 2, 6, 8], "saf": [1, 6, 12, 14, 15], "safar": 16, "safely": [1, 14], "safety": 16, "said": 7, "sair": 13, "sak": 6, "salt": [1, 12], "salv": [4, 6, 11], "sam": [0, 2, 5, 6, 7, 8, 12, 13, 15, 16], "same_sit": 5, "saml": 13, "saml2": 0, "sampl": 13, "san": 4, "sandbox": 16, "sanitiz": [5, 10, 12, 15], "sanitized": 12, "sant": 1, "sap": 6, "sapdb": 6, "sapdbadapt": 6, "sass": 4, "sass_compil": 4, "sav": [1, 5, 6, 11, 12], "saved": [2, 5, 6, 12], "say": 13, "sa\u00edd": [2, 4, 6, 8, 10], "scaffold": [2, 4, 16], "scaffold_zip": 2, "scaffolding": [2, 4, 5, 6, 8, 12, 15, 16], "scal": [5, 8], "scan": 6, "schaf": 1, "schedul": 17, "scheduled": 16, "scheduled_for": 16, "schem": [6, 12], "school": 13, "scor": 16, "score_input": 16, "scratch": 4, "script": [2, 8, 12, 16], "scripting": 10, "scripts": [2, 8, 10], "sdk": 2, "seamlessly": 8, "search": [0, 12, 13, 14, 17], "search_button_text": 14, "search_form": 14, "search_queri": 14, "search_text": 14, "search_valu": 16, "searchabl": 6, "searched": [10, 14], "searching": [6, 10], "second": [6, 10, 12, 13, 15], "seconds": [5, 12], "secret": [5, 13], "secs": 16, "section": [2, 4, 6, 8, 12, 13], "sections": 6, "secur": [0, 12], "securely": 2, "security": [0, 2, 6, 7, 13], "see": [0, 1, 3, 4, 5, 6, 8, 10, 12, 13, 14, 16], "seem": 6, "seen": [0, 5, 7, 10, 12, 13, 14, 16], "seg": 4, "segment": 4, "segred": 13, "segu": [2, 4, 5, 6, 8], "seguint": [2, 4, 5, 6, 8, 10, 11, 12, 13, 14], "segund": 6, "segur": [0, 5], "seguranc": [2, 5, 6, 10], "seis": 6, "sej": [4, 6], "seleccion": 6, "selecion": [3, 6], "select": [4, 5, 7, 11, 12, 13, 14, 15, 16, 17], "selected": [3, 4, 7, 10, 12, 14, 16], "selected_elements": 16, "selected_id": 14, "selectedindex": 16, "selecting": 6, "selection": [12, 14], "selections": 12, "selector": [10, 16], "selector1": 10, "selector2": 10, "selectorn": 10, "selectwidget": 12, "selec\u00e7\u00e3": 6, "sele\u00e7\u00e3": [6, 10], "self": [2, 5, 6, 10, 12, 13, 14, 16], "semantic": [12, 13], "semelh": [0, 4, 5, 6, 10], "sempr": [6, 8], "send": [2, 6, 12, 13, 16], "send_two_factor_email": 13, "sendgrid": 16, "sendgrid_api_key": 16, "sendgridapiclient": 16, "sending": 17, "sendmail": 16, "sendmail_task": 16, "sends": 13, "senh": [0, 2, 3, 5, 6, 13], "sens": [0, 6, 12], "sensitiv": [5, 12], "sens\u00edvel": 6, "sent": [5, 6, 13, 16], "sen\u00e3": 6, "separ": [6, 14], "separat": [4, 6, 12, 13, 16], "separated": [2, 5, 6, 14, 16], "separating": [8, 12], "separator": 12, "sequenc": [5, 6], "sequencial": 6, "sequ\u00eanc": 6, "seq\u00fcenc": 6, "seq\u00fc\u00eanc": 6, "ser": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14], "seri": 1, "serializ": [4, 6, 8, 10], "serializabl": [0, 5, 6], "serialized": [5, 6, 10, 12], "serv": [0, 2, 3, 4, 7, 8, 13, 15, 16, 17], "served": [2, 4, 6], "server_addr": 6, "server_nam": 4, "servers": [2, 6], "serversid": 16, "servidor": [0, 4, 5, 6], "serving": 16, "ser\u00e3": [4, 6, 10], "session": [0, 2, 4, 6, 10, 12, 13, 14, 15, 16, 17], "session_app1": 5, "session_secret_key": 5, "sessions": [0, 6, 15], "sess\u00e3": [4, 17], "sess\u00f5": 5, "set": [0, 2, 5, 7, 8, 10, 11, 14, 15, 16], "set_attribut": 6, "set_encoding": 6, "set_head": 6, "set_password": 3, "setinterval": 16, "sets": [2, 5, 6, 12], "setting": [2, 4, 5, 6, 12, 14], "settings": [1, 4, 5, 6, 10, 13, 15, 16], "setup": [1, 3, 4, 13, 16], "setvirtualfields": 6, "several": 4, "severity": 6, "se\u00e7\u00e3": 6, "se\u00e7\u00f5": 6, "sf": 16, "sftp": 6, "sg": 16, "sh": 16, "sha512": [8, 12], "shar": [5, 6, 15], "shared": 5, "shell": 10, "ships": 8, "sho": 6, "shopping": 5, "short": [12, 16], "shortcut": [6, 8, 12], "should": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "show": [2, 5, 6], "show_id": 14, "showcas": 6, "showed": 13, "showing": [13, 14], "shown": [2, 6, 12, 13], "shows": [4, 8, 12, 13], "shutil": 6, "si": [6, 14], "sid": [6, 13, 16, 17], "sideb": 8, "sidebar_enabled": 8, "sidebar_menu": 10, "sidec": 16, "sign": [5, 8, 13], "signatur": [5, 6, 7, 10, 12, 16], "signed": [2, 5, 12], "signed_url": 5, "signif": [5, 6, 7], "signific": [6, 8], "significant": 8, "signing": 5, "signing_inf": 12, "signs": 5, "sim": 6, "simbol": 2, "simb\u00f3l": 4, "simil": [0, 12, 15], "similar": [6, 10], "similariti": 15, "simpl": [1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 16, 17], "simple_query": 16, "simples": [4, 6, 11], "simplest": 2, "simplicity": [6, 16], "simplific": 6, "simplified": [0, 4, 8], "simply": [2, 4, 5, 6, 8, 12, 16], "simultan": 6, "simult\u00e2n": 6, "sinal": 6, "sinc": [0, 4, 5, 7, 8, 14, 15], "singl": [2, 4, 5, 6, 7, 8, 12, 13, 14], "singleton": [5, 15], "sintax": [4, 6, 10, 13, 17], "sint\u00e1t": 5, "sistem": [0, 2, 5, 6], "sit": [1, 2, 4, 5, 6, 10, 14, 16], "situa\u00e7\u00e3": 6, "siz": [5, 8, 12], "skip": [5, 16], "slash": [1, 4, 5, 13], "sleep": 16, "sleep_tim": 16, "slick": 0, "slow": [6, 16], "slug": 12, "small": [5, 12], "sms": 16, "smtplib": 16, "snippets": 6, "so": [2, 5, 6, 8, 10, 12, 13, 14, 15, 16], "soap": 10, "sob": [4, 6], "sobr": [0, 8], "sobrecarg": 6, "sockets": 16, "solicit": [0, 6], "solt": 6, "solution": [0, 2, 16], "solu\u00e7\u00e3": [1, 6], "som": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 15, 16], "some_condition": 8, "some_form": 12, "some_valu": 6, "somefield": 6, "somefil": 6, "soment": [2, 4, 6, 8], "somepath": 5, "sometabl": 6, "something": [6, 7, 8, 10, 12, 13, 14, 16], "sometim": [5, 6, 8, 10, 12, 13], "somevalu": 6, "somewhat": 12, "somewher": [5, 6], "soon": [8, 16], "sophisticated": 15, "sort": 12, "sorted": 14, "sorting": 12, "sourc": [1, 2, 3, 4, 6, 12, 16], "source1": 12, "south": 1, "sp": 6, "spac": [6, 8, 12, 14], "span": [4, 15], "spatialit": 6, "speaking": 5, "special": [0, 5, 6, 8, 13, 15, 17], "specialization": 6, "specials": 12, "specific": [2, 4, 5, 6, 8, 10, 12, 13, 15, 16], "specifically": [5, 6, 12, 16], "specifications": 7, "specified": [5, 6, 8, 10, 12, 13, 14, 15, 16], "specify": [2, 4, 5, 6, 7, 12, 13, 14, 16], "specifying": 10, "speed": [7, 8], "sphinx": 1, "spiderman": [7, 14], "spin": [0, 4], "spirit": 7, "split": [6, 7, 8, 12], "split_emails": 12, "sql": 17, "sql_mod": 6, "sqladapt": 6, "sqlcustomtyp": 6, "sqlform": [0, 12, 15], "sqlforms": 12, "sqlit": [1, 5, 7, 13, 14, 16], "sqlite3": 6, "sqliteadapt": 6, "squar": 8, "src": [6, 8, 10, 12, 16], "ss": 12, "ssl": [2, 4], "ssl_cert": 2, "ssl_key": 2, "sslcert": 6, "sslkey": 6, "sslmod": 6, "sslrootcert": 6, "sso_id": [5, 13], "stabl": 2, "stand": 12, "standard": [1, 2, 3, 6, 7, 13, 14, 15, 16, 17], "standards": [12, 14], "stands": 10, "start": [1, 2, 3, 4, 5, 6, 10, 12, 13, 14, 16], "start_impersonating": 13, "started": 16, "starting": [2, 4, 12], "starts": [0, 3, 5, 8, 15], "startup": [6, 15], "stat": [5, 7, 15, 16], "stated": 5, "stateful": 5, "stateless": [5, 16], "statement": 8, "statements": [8, 12], "static": [1, 4, 8, 10, 15], "static_dev": 4, "status": [7, 16], "status_cod": 16, "stderr": [2, 16], "stdout": [2, 16], "steil": [0, 1, 14], "step": [13, 15], "step1": 5, "step2": 5, "step3": 5, "step_completed": 5, "stepping": 16, "steps": 2, "still": [5, 6, 10, 12, 14, 15], "ston": 14, "stop": [2, 13], "stop_impersonating": 13, "stor": [5, 6, 12, 13, 16], "storag": [5, 6, 7, 14], "stored": [0, 5, 6, 12, 13, 15, 16], "stored_it": 6, "stored_item_archiv": 6, "storing": 5, "story": 0, "str": [2, 5, 6, 10, 12, 15, 16], "stre": [5, 6], "streaming": [4, 15], "strength": 7, "strict": 12, "strictly": [4, 6, 16], "string": [4, 5, 6, 7, 10, 11, 12, 14], "stringi": 6, "stringlistproperty": 6, "strings": [10, 12, 14], "strip": 12, "stripped": [2, 15], "strong": [0, 10, 13], "strongly": [1, 5, 12, 14], "structur": [1, 4, 6, 13, 14, 15, 17], "stuck": 2, "students": 2, "studi": 1, "study": 1, "stuff": [12, 14], "style": [4, 6, 8, 12, 16, 17], "styles": 14, "stylesheet": [8, 14], "styling": 14, "sub": 6, "subcl": 6, "subclassing": 12, "subconjunt": [0, 6], "subfold": 6, "subfolders": [5, 6], "subheadings": 10, "subject": [6, 7, 13, 16], "sublinh": 6, "submet": 12, "submission": [12, 16], "submit": [1, 5, 6, 10, 12, 14], "submits": 13, "submitted": [13, 14, 16], "submitting": 13, "subm\u00f3dul": 0, "subnet": 12, "subnets": 12, "subpast": 4, "subqueri": 16, "subset": 12, "subse\u00e7\u00e3": 6, "substitu": [0, 2, 6, 8, 10], "substitui\u00e7\u00f5": 14, "substitutions": 8, "substitu\u00edd": 6, "substring": [6, 12], "succeded": 13, "succeeded": 0, "success": [5, 7, 16], "successful": [0, 13], "successfully": 13, "suced": 6, "sucess": [4, 6], "such": [4, 6, 10, 12, 13, 14, 15], "sud": [2, 13], "suffered": 0, "suffers": 13, "sufficient": 13, "suficient": [6, 14], "suger": [1, 6], "sugest\u00f5": 17, "suggest": [1, 12], "sugiz": 0, "sup": 7, "super": 6, "superher": [6, 7, 12, 14, 16], "superhero": 7, "superior": 11, "superman": [6, 7, 12, 14], "superpotent": 7, "superpow": [6, 7], "superseeded": 6, "supond": [2, 6], "suponh": 6, "supor": 6, "suport": [0, 11, 17], "supplied": 10, "support": [1, 2, 6, 12, 14, 15], "supported": [11, 12, 13], "supporting": 16, "supports": [4, 5, 8, 10, 12, 16], "suppress": 2, "suprim": 10, "sur": [5, 12, 13, 16], "surely": [3, 4], "surrounding": 12, "susan": 6, "sutil": 6, "sv": 6, "switch": [2, 5, 6], "sybas": 6, "sybaseadapt": 6, "symbol": 12, "symbols": 12, "sync": [2, 6], "synops": 12, "syntactic": 12, "syntax": [0, 1, 4, 5, 6, 7, 8, 10, 12, 13, 15, 16], "system": [0, 2, 5, 6, 10, 13, 16], "systems": 16, "sysus": 6, "s\u00e3": [0, 1, 3, 4, 5, 6, 8, 10, 11, 13, 14], "s\u00e9ri": 6, "t": [0, 2, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15], "t_fold": 5, "tab": [3, 5, 12], "tabel": [5, 10, 13, 14, 15], "tabl": [5, 7, 12, 13, 14, 15, 16, 17], "table1": 6, "table_hash": 6, "table_nam": 6, "tablenam": [6, 7, 12, 16], "tag": [0, 5, 6, 7, 8, 13, 15, 16], "tag_input": 16, "tagg": 10, "tagged_db": 13, "tagging": [6, 10, 13], "tags": [0, 5, 6, 8, 10, 12, 15, 16, 17], "tags_input": 16, "tags_inputs": 16, "tail": 6, "tail_nam": 13, "tais": 6, "tak": [2, 3, 5, 6, 8, 12, 13, 14, 16], "taken": 12, "tal": 6, "talk": 4, "talvez": [2, 5], "tamanh": 6, "tampering": [5, 6], "tant": [6, 16], "tantissim": 11, "tap": 16, "tar": 12, "tard": [4, 6], "taref": [1, 6, 13], "target": [10, 16], "task": 17, "task_run": 16, "tasks": [5, 6, 16], "tast": 6, "tbody": 6, "tcp": 3, "td": 6, "teach": 13, "technically": 16, "tecl": [6, 11], "tell": [4, 13, 16], "telling": [2, 13], "tells": [5, 16], "temp": [2, 8], "templat": [0, 4, 10, 12, 13, 15, 16, 17], "temporarily": 5, "tempor\u00e1ri": 6, "ten": 12, "tenancy": 6, "tenh": [1, 5, 6], "tent": [5, 6], "ter": [2, 4, 5, 6, 10], "teradat": 6, "teradataadapt": 6, "terceir": 6, "tered": 12, "term": 6, "termin": 10, "terminal": 4, "terminat": [8, 16], "termination": 4, "terms": 12, "terr": 6, "ter\u00e3": 6, "test": [2, 6, 8, 10, 12, 14], "tested": [0, 2, 10, 13], "testing": [4, 6], "text": [2, 5, 6, 8, 10, 14, 16], "textar": 12, "textareawidget": 12, "textual": 10, "th": 6, "than": [0, 5, 6, 8, 10, 12, 13, 14, 16], "thank": 10, "thanks": 0, "that": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16], "that_templat": 8, "the": [0, 3, 8, 10, 11, 13, 15, 17], "thead": 6, "them": [0, 1, 2, 4, 5, 6, 7, 8, 12, 13, 14, 15, 16], "themselv": [6, 8], "then": [2, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "ther": [1, 2, 4, 5, 6, 8, 12, 13, 14, 15, 16], "therefor": [4, 5, 8, 12, 15], "thes": [2, 4, 5, 6, 8, 10, 12, 14, 16], "they": [2, 4, 5, 6, 7, 8, 12, 13, 15, 16], "thing": [5, 6, 12, 14, 15], "thing_id": 12, "thing_tags_default": 6, "things": [2, 5, 6, 12], "think": [5, 8, 14, 16], "third": [0, 5, 12], "this": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "this_templat": 8, "thisisatest": 10, "thisisthekey": 12, "thos": [5, 6, 8, 10, 13, 15, 16], "though": 8, "thought": [0, 6, 13], "thre": [0, 5, 6, 13], "thread": [6, 15, 16], "threaded": [2, 15], "threads": [5, 6], "threadsafevariabl": 5, "through": [2, 12], "throughout": 5, "thumbnail": 12, "ti": 5, "ticket": 6, "tickets_only": 2, "til": 6, "tim": [0, 4, 5, 6, 8, 13, 14, 15, 16], "timed": 6, "timedelt": 12, "timeoffset": 10, "timeout": [5, 16], "timeouts": 16, "timestamp": [5, 7, 16], "tint": 4, "tip": [4, 14], "tips": [2, 14], "tir": 6, "titl": [8, 12, 16], "tiv": 6, "tmp": [5, 6], "to": [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16], "to_addr": 16, "to_addrs": 16, "tod": [1, 2, 3, 4, 5, 7, 8, 10, 11, 13, 14, 16], "today": [0, 12, 16], "togeth": [0, 8], "token": [5, 12], "tom": 6, "too": [2, 5, 6, 12, 16], "took": 6, "tool": 0, "tools": [6, 13, 16], "top": [8, 12, 13], "topics": [14, 17], "torn": [1, 2, 5, 6, 10, 14], "total": [1, 6, 7], "total_pric": 6, "totp": 13, "touch": 8, "toy": 6, "tr": 6, "trabalh": [2, 4, 6, 7], "traceback": 6, "tracebacks": 5, "track": [1, 15], "trad": 6, "tradicion": 6, "tradicional": 6, "traditional": 2, "tradutor": 4, "traduz": [1, 6, 8, 11], "tradu\u00e7\u00e3": [5, 17], "tradu\u00e7\u00f5": [5, 11], "trailing": [2, 12], "training": 1, "transaction": [5, 6], "transactional": 16, "transactions": 6, "transa\u00e7\u00f5": 6, "transform": [4, 5, 6, 12, 16], "transformed": [5, 6], "transforms": 5, "transitions": 16, "translat": 16, "translated": [5, 8, 12], "translation": [5, 12, 15], "translations": [5, 11, 16], "translator": [2, 11, 17], "transmit": 3, "transparent": [6, 8, 12], "transparently": 8, "trapped": 16, "trat": [4, 6], "tre": [8, 12], "treated": [6, 15], "tri": [6, 13, 15], "trickery": 8, "tricks": 2, "tried": 0, "trigg": [6, 16], "triggers": 5, "trivial": [5, 6], "tru": [1, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "trunc": 6, "truncat": 6, "truth": 16, "try": [2, 5, 6, 8, 12, 13, 16], "trying": [1, 12, 13], "tr\u00e1s": 6, "tr\u00eas": 6, "ttl": 5, "tud": [5, 6], "tupl": [6, 8, 10], "turn": [12, 13, 14, 16], "turned": 0, "turns": [6, 16], "tutorial": [1, 2, 4, 14, 16], "tutorials": 1, "twic": [5, 6], "twili": 16, "twitt": [0, 5, 13], "two": [1, 2, 4, 5, 6, 8, 12, 14, 15, 16], "two_factor": 13, "txt": [0, 2, 4, 6], "type": [2, 5, 7, 8, 10, 13, 16], "types": 12, "typical": [5, 6, 12], "typically": [4, 8], "t\u00eam": [2, 4, 6, 13], "t\u00edpic": 6, "t\u00edtul": [3, 10, 14], "t\u00f3pic": 6, "u": [2, 6, 10], "ubuntu": 13, "uc": 1, "ui": [5, 13], "uid": 6, "ul": [8, 12, 13, 16], "un": [8, 10, 11, 16], "unauthenticated": [5, 6, 10], "unauthorized": 6, "unchanged": 12, "undefined": 5, "under": [2, 3, 4, 5, 6, 12], "underlying": 15, "underscor": [6, 10, 12], "understand": [0, 2, 4, 6, 7, 8], "understanding": 17, "undocumented": 16, "unfortunat": 6, "unfortunately": 14, "unicod": [6, 12], "unicodedecodeerror": 6, "unid": 6, "uniform": 6, "uniqu": [6, 7, 12], "unit_pric": 6, "unit\u00e1ri": 6, "universal": [4, 6, 10], "uni\u00e3": 6, "unknown": 4, "unless": [3, 5, 6, 8, 12, 16], "unlik": [0, 2, 7, 8, 15, 16], "unnamed": 6, "unneded": 2, "unordered": 10, "unpkg": 16, "unquoted": 10, "uns": 5, "unsaf": [5, 10, 12], "untested": [2, 13], "until": [0, 5, 6, 8, 12], "un\u00e1ri": 6, "up": [1, 2, 5, 6, 8, 13, 16], "updat": [2, 5, 12, 14, 15, 16], "update_form": 12, "update_languag": 11, "update_naiv": 6, "update_thing": 12, "updated": [5, 6, 12], "updating": 5, "upgrad": [2, 6], "upgraded": 2, "upload": [4, 6], "upload_fold": [6, 12], "upload_help": 16, "uploaded": [6, 12, 15], "uploadfield": 6, "uploadfold": 6, "uploadfs": 6, "uploads": 6, "uploadseparat": 6, "upon": [2, 13, 15], "upper": [4, 5, 12], "upper_cas": 5, "uppercas": [5, 12], "uri": 13, "uris": 6, "url": [4, 5, 7, 8, 12, 13, 14, 15, 16], "url_prefix": 2, "url_sign": 5, "url_to_post_t": 16, "urls": [2, 4, 12, 15], "urlsign": 17, "us": [2, 12], "usa": [0, 4, 5, 6, 8], "usabl": 14, "usad": [0, 4, 6, 10, 11, 13, 14], "usag": [2, 3, 4, 5, 6, 10, 12, 13, 14], "usam": 6, "usand": [4, 5, 8, 10, 13, 17], "usar": [2, 4, 5, 6, 8, 10, 14], "use": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "use_schedul": 16, "used": [2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "useful": [2, 4, 8, 10, 12, 13, 14, 16], "usefull": 12, "useless": [12, 15], "user": [2, 4, 5, 6, 7, 8, 10, 12, 14, 15, 16], "user_email": 15, "user_id": [5, 6, 13, 15], "user_nam": 6, "user_outside_network": 13, "user_password": 6, "user_token": 6, "usernam": [5, 6, 13], "users": [0, 1, 4, 5, 13, 15], "uses": [0, 1, 2, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "using": [0, 1, 3, 4, 7, 12, 15, 17], "uso": [2, 5, 14], "usos": 6, "usou": [5, 6], "usr": 1, "usual": [2, 8, 10, 12], "usually": [1, 5, 7, 12], "usu\u00e1ri": [1, 2, 3, 4, 5, 6, 13, 15], "us\u00e1": [2, 6, 8], "utcnow": [5, 6], "utf": 6, "utf8": 6, "utf8mb4": 6, "utility": [2, 12], "utiliz": [2, 3, 4, 5, 6, 10, 13, 14], "utilizing": 14, "utils": [2, 4, 5, 8, 10, 12, 13, 14, 15, 17], "uuid": [5, 6], "uuid4": [5, 6], "uuids": 6, "v": [5, 10, 12, 16], "v3": 1, "vai": [1, 4, 6, 8], "val": 14, "val1_row1": 6, "val1_row2": 6, "val2_row1": 6, "val2_row2": 6, "valid": [4, 6, 10, 13, 15, 17], "validat": [6, 12, 13], "validate_cod": 13, "validate_js": 4, "validated": 12, "validating": 13, "validation": [4, 6, 7, 8, 13, 14], "validator": [6, 12], "validators": [2, 15], "validity": 12, "valios": 1, "valor": [5, 10, 11, 12, 14], "valq7711": [0, 4], "vals": 16, "valu": [2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16], "value1": 6, "value2": 6, "value_field": 12, "valued": 12, "vam": [1, 6, 10], "vantag": 6, "vantagens": 6, "var": 16, "varch": 6, "vari": 6, "variabl": [5, 6, 10, 12, 14, 16], "various": 5, "vari\u00e1vel": [4, 6, 10, 11], "varredur": 6, "vars": [6, 10, 12, 15], "vaz": 6, "vazi": [2, 6], "ve": [2, 3, 5, 8, 10, 12, 13, 14, 16], "vej": [2, 3], "veloc": 6, "vem": [0, 4, 6], "vendor": 16, "vendor_typ": 16, "vendors": 16, "venv": 2, "ver": [2, 6, 13], "verd": 4, "verdad": 14, "verdadeir": [6, 12, 14], "verif": 6, "verific": [5, 6, 13], "verification": 13, "verified": [5, 12, 16], "verify": 5, "verify_email": 13, "verifying": 13, "vermelh": 4, "vers": 5, "version": [1, 3, 7, 8, 12, 16], "versions": [2, 5, 12], "vers\u00f5": 6, "vertic": 6, "verticaadapt": 6, "very": [0, 4, 8, 10, 12, 13, 15, 16], "vez": [2, 4, 5, 8, 10, 13, 14], "ve\u00edcul": 5, "via": [5, 6, 8, 10, 16], "vias": 6, "vic": 5, "vid": [1, 2, 16], "view": [1, 5, 6], "viewing": 6, "viewport": 8, "views": 6, "vincul": 6, "vind": 2, "vir": 13, "virtual": [1, 17], "virtualenv": [1, 2], "virtualfields": 6, "visit": [3, 5, 12], "visit_log": 5, "visited": [5, 8], "visiting": [5, 13], "visitor": 6, "visitors": [10, 12], "visits": 6, "vist": [5, 6, 10], "visual": 1, "visualiz": 6, "vis\u00e3": 6, "vis\u00edvel": 4, "vital": 13, "voc": [1, 2, 3, 4, 5, 6, 8, 10, 13, 14], "volt": [5, 6], "vou": 2, "vscod": 2, "vue": [0, 4, 16], "vulner": 8, "v\u00e1l": 4, "v\u00e1r": [2, 4, 5, 6], "v\u00e1ri": [4, 5, 6, 11], "v\u00e3": 6, "v\u00ea": 6, "v\u00edrgul": 6, "v\u00f4o": 7, "w": [2, 4, 5, 6], "w2p_even": 6, "w2p_odd": 6, "waitress": 2, "want": [2, 4, 5, 6, 8, 12, 16], "wanting": 16, "wants": 5, "warning": [2, 5], "was": [0, 5, 6, 12, 13, 14], "watch": [2, 3, 17], "watched": 4, "way": [2, 3, 5, 6, 8, 10, 12, 13, 15], "wayn": [7, 14], "ways": [0, 2, 4, 6, 13, 14, 16], "wb": 6, "we": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "web": [0, 1, 2, 5, 6, 8, 13, 14, 15, 16, 17], "web2py": [0, 1, 2, 3, 4, 5, 6, 12, 13, 14, 17], "websit": [6, 16], "websocket": 16, "welcom": [4, 5, 8, 15, 16], "well": [0, 5, 6, 8, 12, 13, 16], "wer": [0, 4], "what": [4, 5, 6, 8, 12, 13, 15, 16], "whatev": [10, 16], "when": [0, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "whenev": 4, "wher": [1, 2, 4, 6, 8, 12, 13, 15, 16], "wheth": [5, 6, 12, 13, 15], "which": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "whichev": 16, "whil": [4, 5, 6, 7, 12, 13, 14, 15], "whit": [10, 16], "whitelist": 13, "who": [0, 13], "whol": [4, 12], "whos": [6, 8, 12], "why": [5, 8, 15], "widget": [6, 12], "widgets": [16, 17], "width": [8, 12], "wik": [6, 7], "wikiped": [6, 7], "wild": 6, "wildcard": 4, "will": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "window": [5, 8, 12], "windows": [1, 2, 4, 6], "wish": 16, "wishing": 12, "wit": 2, "with": [0, 1, 2, 3, 4, 5, 7, 8, 10, 16, 17], "with_al": 6, "within": [0, 1, 5, 6, 8, 10, 12, 16], "without": [6, 8, 10, 16, 17], "wolf": 0, "wood": 6, "words": [5, 14], "work": [0, 2, 4, 5, 6, 8, 10, 12, 16], "worked": [0, 16], "workers": 2, "workflow": [5, 17], "working": [2, 12], "workload": 6, "workplac": 1, "works": [2, 6, 8, 12, 13, 14, 15, 16], "workspacefold": 2, "world": [4, 5, 6, 8, 10, 12, 16], "worry": 4, "worth": 8, "would": [0, 4, 5, 6, 10, 12, 13, 16], "wouldn": 6, "wrap": [12, 14, 16], "wrapp": 16, "wrapped": 5, "wrappers": 15, "writ": [2, 4, 5, 6, 8], "writabl": [5, 6, 12, 15], "writing": [4, 6, 15], "written": [1, 8, 12, 14], "wrong": [0, 5, 6], "wsgi": 5, "wsgiref": 2, "wsgirefthreadingserv": 2, "wsgith": 2, "www": [2, 8, 10, 14, 16], "x": [2, 4, 6, 8, 10, 12, 14, 16], "xml": [8, 12, 15, 16], "xmlescap": 10, "xmlns": 10, "xss": [6, 8, 10], "xyz": [10, 12], "y": [2, 10, 12], "yaml": 2, "yatl": [0, 4, 5, 6, 12, 14, 16, 17], "yb": 10, "year": [12, 16], "yes": [2, 6, 16], "yes_or_n": 6, "yet": [0, 2, 4, 5, 8, 12, 16], "yml": 2, "you": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16], "your": [1, 2, 3, 4, 5, 6, 8, 10, 12, 13, 14, 15, 16], "your_app": 12, "your_full_path_to_py4web": 1, "your_nam": [1, 16], "yourapp": 6, "yourappnam": 2, "youremail": 13, "yourself": [1, 12], "youtub": [2, 6], "yyyy": 12, "z": [10, 12], "zanferrar": 0, "zap": 13, "zap_id": 13, "zapp": 13, "zapped": 13, "zer": [6, 8, 12, 14], "zip": [0, 2, 6, 12], "zip_cod": 16, "zxjdbc": 6, "\u00c0s": [6, 10], "\u00e1rvor": 4, "\u00e2mbit": 4, "\u00e9": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 17], "\u00e9poc": 6, "\u00edndic": 4, "\u00f3bvi": [6, 8], "\u00f3ptim": 4, "\u00faltim": [5, 6, 13], "\u00fanic": [0, 2, 6, 10], "\u00fate": [1, 6], "\u00fatil": 6}, "titles": ["O que \u00e9 py4web?", "Ajuda, recursos e dicas", "Instala\u00e7\u00e3o e coloca\u00e7\u00e3o em funcionamento", "O Dashboard", "Creating an app", "Fixures", "The Database Abstraction Layer (DAL)", "The RestAPI", "Linguagem de template YATL", "<no title>", "Helpers YATL", "Internacionaliza\u00e7\u00e3o", "Foruml\u00e1rios", "Authentication and authorization", "Rede", "De web2py para py4web", "Advanced topics and examples", "py4web: o manual de refer\u00eancia"], "titleterms": {"A": [3, 6, 10, 12], "As": 1, "Comando": 6, "Como": 1, "De": 15, "Do": 4, "EM": 10, "Em": 4, "Este": 1, "Mais": 6, "O": [0, 1, 3, 4, 5, 6], "OS": 15, "Os": [6, 14], "Um": [1, 6], "_lastsql": 6, "_scaffold": 4, "about": 5, "abstraction": 6, "accessing": 15, "acknowledgments": 0, "actions": [7, 13], "adapt": 6, "adicion": 6, "advanced": [12, 16], "agrup": 6, "ajud": 1, "aka": 2, "alias": 6, "alon": 6, "amostr": 14, "an": 4, "and": [6, 7, 8, 12, 13, 14, 15, 16], "antig": 6, "any_of": 12, "anywher": 5, "aplic": 6, "app": [2, 4, 6], "apps": 4, "args": 15, "arquiv": 11, "as_dict": 6, "as_list": 6, "assinatur": 6, "asynci": 16, "atalh": 6, "atribut": 6, "atualiz": [6, 11], "aut": 6, "autentic": 13, "auth": [5, 13, 15], "authentication": 13, "authorization": 13, "autocomplet": 16, "avanc": 6, "avg": 6, "a\u00e7\u00e3": 14, "background": 16, "banc": 6, "bas": 6, "basic": [12, 14], "beautify": 10, "belongs": 6, "bin\u00e1ri": 2, "block": 8, "body": 10, "bot\u00e3": 14, "bot\u00f5": 14, "built": 10, "b\u00e1sic": 8, "cach": 6, "caching": 5, "cad": 6, "call": 2, "callabl": 14, "calling": 15, "camp": [6, 14], "caracter\u00edst": 6, "cas": 6, "cascad": 6, "cat": 10, "caveats": 5, "celery": 16, "cham": 6, "chang": 4, "chav": 6, "checkbox": [12, 14], "children": 10, "class": 14, "cleanup": 12, "client": 5, "coalesc": 6, "coalesce_zer": 6, "coloc": 2, "columns": 14, "comando": 2, "combin": 6, "commit": 6, "complexity": 12, "comput": 6, "computed": 6, "comuns": 6, "condition": 5, "conex\u00e3": 6, "conex\u00f5": 6, "configur": [2, 6], "constructor": 12, "construtor": 6, "consult": 6, "cont": 6, "contains": 6, "conte\u00fad": 17, "contribu": 1, "control": 6, "convenient": 5, "conversion": 15, "cooki": 5, "copi": 6, "copying": 4, "corr": 2, "count": [6, 15], "creating": 4, "crud": 14, "crypt": 12, "csv": 6, "custom": [12, 14], "customizing": 14, "dad": 6, "dal": [5, 6], "dashboard": [3, 6], "dat": 12, "databas": [5, 6, 12], "datastor": 6, "day": 6, "db": 6, "decor": 5, "def": 8, "default": 8, "defeit": 6, "defin": 6, "define_tabl": 6, "deix": 6, "delet": 6, "deployment": 2, "depur": 1, "design": [2, 12], "development": 8, "dic": 1, "dicion\u00e1ri": 6, "dictionari": 12, "din\u00e2m": 4, "discord": [1, 13], "distinct": 6, "distint": 6, "distribu\u00edd": 6, "div": 10, "dock": 2, "dom": 10, "domain": 4, "drop": 6, "elif": 8, "else": 8, "endswith": 6, "engin": 2, "envi": 6, "environment": 2, "equality": 12, "estil": 6, "est\u00e1t": 4, "etiquet": 13, "exampl": [7, 12, 14, 15, 16], "excet": 8, "exclud": 6, "exclus\u00e3": 6, "execu": 6, "executesql": 6, "experiment": 6, "experimental": 6, "export": 6, "express\u00f5": 6, "extend": 8, "extending": 8, "facebook": 13, "factor": 13, "fake_migrat": 6, "falh": 6, "faz": 6, "featur": 14, "fich": 6, "field": [6, 12], "fields": 6, "fil": [4, 12, 15], "filter_in": 6, "filter_out": 6, "filtering": 14, "filtr": 6, "finally": 8, "find": [6, 10], "first": 6, "fixa\u00e7\u00e3": 6, "fixtur": 5, "fixur": 5, "flash": [5, 15], "font": [1, 2], "form": [10, 12, 15, 16], "format": [6, 12, 16], "forms": 12, "formul\u00e1ri": 12, "foruml\u00e1ri": 12, "from": 2, "funcion": 2, "functions": [8, 12], "gae": 2, "gcloud": 2, "generating": 6, "get": 7, "github": 1, "global": 2, "googl": [1, 2, 6, 13], "grid": [14, 15, 16], "grids": 14, "groupby": 6, "grup": 1, "h1": 10, "h2": 10, "h3": 10, "h4": 10, "h5": 10, "h6": 10, "having": 6, "head": 10, "hell": 15, "helpers": 10, "heranc": 6, "hour": 6, "html": [6, 10], "htmx": 16, "https": 2, "i": 10, "id": 6, "if": 8, "ilik": 6, "img": 10, "impersonation": 13, "implant": 2, "import": 6, "in": [5, 8, 10, 16], "includ": 8, "inferior": 6, "information": 8, "inject": [5, 10], "inner": 6, "input": 10, "inser": 6, "insert": 6, "inser\u00e7\u00e3": 6, "insid": 13, "instal": 2, "installations": 2, "installing": 2, "interfac": 13, "internacionaliz": 11, "introduction": 6, "is_alphanumeric": 12, "is_dat": 12, "is_date_in_rang": 12, "is_datetim": 12, "is_datetime_in_rang": 12, "is_decimal_in_rang": 12, "is_email": 12, "is_empty_or": 12, "is_equal_t": 12, "is_expr": 12, "is_fil": 12, "is_float_in_rang": 12, "is_imag": 12, "is_in_db": 12, "is_in_set": 12, "is_int_in_rang": 12, "is_ipaddress": 12, "is_ipv4": 12, "is_ipv6": 12, "is_json": 12, "is_length": 12, "is_list_of": 12, "is_list_of_emails": 12, "is_low": 12, "is_match": 12, "is_not_empty": 12, "is_not_in_db": 12, "is_null_or": 12, "is_saf": 12, "is_slug": 12, "is_strong": 12, "is_tim": 12, "is_upload_filenam": 12, "is_upp": 12, "is_url": 12, "isempty": 6, "iter": 6, "join": 6, "joins": 6, "js": 16, "junt": 6, "key": 14, "label": 10, "last": 6, "lay": 6, "layout": 8, "ldap": 13, "left": 6, "leg": 6, "len": 6, "less": 6, "li": 10, "lik": 6, "limitby": 6, "linguag": 8, "linh": 2, "list": 6, "local": [1, 2, 6], "low": 6, "l\u00f3gic": 6, "manipulation": 12, "manual": [1, 17], "many": 6, "mapped": 4, "marca\u00e7\u00e3": 6, "max": 6, "melhor": 2, "memcach": 5, "memoiz": 5, "mem\u00f3r": 6, "mesm": 6, "messag": 16, "methods": [6, 15], "microsoft": 6, "migrat": 6, "migra\u00e7\u00e3": 6, "migra\u00e7\u00f5": 6, "min": 6, "minimal": 12, "minut": 6, "mobil": 8, "model": [4, 6], "modern": 1, "modific": 6, "month": 6, "mssql": 6, "muit": 6, "multipl": [5, 13], "mysql": 6, "new_app": 2, "nom": 6, "nosql": 6, "not": 6, "nov": 6, "oauth2": 13, "object": [14, 16], "objects": 13, "objet": 4, "obten\u00e7\u00e3": 6, "ol": 10, "on": 2, "on_defin": 6, "oper": 6, "option": 10, "options": 12, "op\u00e7\u00e3": 2, "op\u00e7\u00f5": 2, "orden": 6, "orderby": 6, "orderby_on_limitby": 6, "or\u00e1cul": 6, "other": 12, "out": 6, "outr": 6, "overview": 10, "p": 10, "padr\u00e3": 6, "pag": 8, "palavr": 6, "pam": 13, "par": [6, 8, 15], "paramet": 12, "parameters": 14, "part": [2, 6], "par\u00e2metr": 6, "past": 6, "pegadinh": 6, "permiss\u00f5": 13, "personaliz": [5, 6, 10, 14], "pip": 2, "plataform": 2, "plugins": 13, "plural": 6, "pluraliz": 11, "podman": 2, "polic": 7, "polymodel": 6, "pool": 6, "practical": 7, "pre": 10, "preguic": 6, "primarykey": 6, "primeir": 2, "prim\u00e1r": 6, "principal": 3, "princ\u00edpi": 4, "proced": 2, "pr\u00e9": [1, 2], "py4web": [0, 1, 6, 15, 16, 17], "pycharm": 1, "python": 1, "pythonanywher": 2, "p\u00e1gin": [3, 4], "q": 16, "quebr": 6, "query": 6, "quick": 6, "quoting": 6, "rang": 12, "raw": 6, "real": 6, "record": 6, "recurs": [1, 6], "red": [5, 14], "redefin": 6, "redirect": 15, "referent": [6, 14, 17], "regexp": 6, "registr": 6, "relation": 6, "relations": 6, "rela\u00e7\u00e3": 6, "remot": 6, "renderiz": 6, "replic": 6, "represent": 6, "request": 4, "requisit": [1, 2], "reserv": 6, "respons": 7, "restap": 7, "resum": 6, "retorn": [4, 6], "return": [8, 15], "returning": 15, "rnam": 6, "rollback": 6, "rot": 4, "row": 6, "rows": 6, "run": 2, "s": 6, "schedul": 16, "script": 10, "searching": 14, "seconds": 6, "security": 12, "segur": 6, "select": [6, 10], "selects": 6, "selet": 6, "sending": 16, "sequence_nam": 6, "serv": [1, 5, 6, 10], "session": 5, "sessions": 5, "sess\u00e3": 3, "set": [6, 12], "set_password": 2, "setting": 15, "settings": 14, "setup": 2, "sharing": 5, "shell": [2, 6], "sid": [5, 10], "sidec": 12, "simpl": 15, "sincroniz": 6, "singul": 6, "sintax": 8, "sobr": 6, "sort": 6, "sorted": 12, "span": 10, "special": [2, 12], "sql": 6, "sqlit": 6, "stand": 6, "standard": [8, 12], "startswith": 6, "string": 16, "strings": 6, "structur": [8, 12], "style": [10, 14], "substrings": 6, "sugest\u00f5": 1, "sum": 6, "sup": 8, "suport": [2, 6], "supported": 6, "t": 16, "tabel": [6, 17], "tabl": [6, 10], "table_class": 6, "tag": 10, "tagging": 12, "tags": 13, "task": 16, "tbody": 10, "td": 10, "temp": 6, "templat": [5, 8, 14], "temporiz": 6, "tend": 6, "tent": 8, "tentat": 6, "text": 12, "textar": 10, "th": 10, "the": [1, 2, 4, 5, 6, 7, 12, 14, 16], "thead": 10, "tim": 12, "tip": 6, "titl": 10, "to": 6, "tod": 6, "topics": 16, "tour": 6, "tr": 10, "trabalh": 1, "tradu\u00e7\u00e3": 11, "transa\u00e7\u00e3": 6, "translator": 5, "trigger_nam": 6, "tt": 10, "tupl": 12, "tutori": 1, "two": 13, "two_factor_required": 13, "two_factor_send": 13, "two_factor_tri": 13, "two_factor_validat": 13, "type": [6, 12], "types": 6, "ubuntu": 2, "ul": 10, "understanding": 2, "up": 15, "updat": 6, "update_or_insert": 6, "update_record": 6, "upload": 12, "upper": 6, "uri": 6, "url": 10, "urlsign": 5, "usag": 16, "usand": [6, 14], "user": 13, "using": [2, 5, 6, 8, 10, 13, 14, 16], "uso": 6, "utils": 16, "valid": 12, "validate_and_insert": 6, "validate_and_updat": 6, "validation": 12, "validators": [6, 12], "valor": [4, 6], "variabl": [8, 15], "velh": 6, "version": [2, 6], "vez": 6, "view": 15, "virtu": 6, "virtual": [2, 6], "vscod": 1, "v\u00edd": 1, "watch": 4, "web": [3, 4], "web2py": 15, "whil": 8, "widget": 16, "widgets": 12, "with": [6, 12, 13, 14, 15], "without": [2, 12], "workflow": 8, "world": 15, "wsgi": 2, "xml": [6, 10], "yatl": [8, 10], "year": 6, "\u00e9": 0, "\u00edndic": [6, 17]}}) \ No newline at end of file diff --git a/apps/_scaffold/__init__.py b/apps/_scaffold/__init__.py index aecd78c30..91ea9f4bb 100644 --- a/apps/_scaffold/__init__.py +++ b/apps/_scaffold/__init__.py @@ -6,6 +6,9 @@ # by importing db you expose it to the _dashboard/dbadmin from .models import db +# import the scheduler +from .tasks import scheduler + # by importing controllers you expose the actions defined in it from . import controllers diff --git a/apps/_scaffold/common.py b/apps/_scaffold/common.py index 245c72a73..428f7499c 100644 --- a/apps/_scaffold/common.py +++ b/apps/_scaffold/common.py @@ -4,31 +4,20 @@ """ import os import sys -import logging from py4web import Session, Cache, Translator, Flash, DAL, Field, action +from py4web.server_adapters.logging_utils import make_logger from py4web.utils.mailer import Mailer from py4web.utils.auth import Auth from py4web.utils.downloader import downloader from pydal.tools.tags import Tags +from pydal.tools.scheduler import Scheduler from py4web.utils.factories import ActionFactory from . import settings # ####################################################### # implement custom loggers form settings.LOGGERS # ####################################################### -logger = logging.getLogger("py4web:" + settings.APP_NAME) -formatter = logging.Formatter( - "%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s" -) -for item in settings.LOGGERS: - level, filename = item.split(":", 1) - if filename in ("stdout", "stderr"): - handler = logging.StreamHandler(getattr(sys, filename)) - else: - handler = logging.FileHandler(filename) - handler.setFormatter(formatter) - logger.setLevel(getattr(logging, level.upper(), "DEBUG")) - logger.addHandler(handler) +logger = make_logger("py4web:" + settings.APP_NAME, settings.LOGGERS) # ####################################################### # connect to db @@ -52,6 +41,7 @@ # ####################################################### if settings.SESSION_TYPE == "cookies": session = Session(secret=settings.SESSION_SECRET_KEY) + elif settings.SESSION_TYPE == "redis": import redis @@ -64,11 +54,13 @@ else cs(k, v, e) ) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) + elif settings.SESSION_TYPE == "memcache": import memcache, time conn = memcache.Client(settings.MEMCACHE_CLIENTS, debug=0) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) + elif settings.SESSION_TYPE == "database": from py4web.utils.dbstore import DBStore @@ -84,7 +76,7 @@ auth.param.login_after_registration = settings.LOGIN_AFTER_REGISTRATION auth.param.allowed_actions = settings.ALLOWED_ACTIONS auth.param.login_expiration_time = 3600 -auth.param.password_complexity = {"entropy": 50} +auth.param.password_complexity = {"entropy": settings.PASSWORD_ENTROPY} auth.param.block_previous_password_num = 3 auth.param.default_login_enabled = settings.DEFAULT_LOGIN_ENABLED auth.define_tables() @@ -135,13 +127,15 @@ ) if settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE: - from py4web.utils.auth_plugins.oauth2google_scoped import OAuth2GoogleScoped # TESTED + from py4web.utils.auth_plugins.oauth2google_scoped import ( + OAuth2GoogleScoped, + ) # TESTED auth.register_plugin( OAuth2GoogleScoped( secrets_file=settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE, - scopes=[], # Put here any scopes you want in addition to login - db=db, # Needed to store credentials in auth_credentials + scopes=[], # Put here any scopes you want in addition to login + db=db, # Needed to store credentials in auth_credentials ) ) @@ -183,10 +177,12 @@ # files uploaded and reference by Field(type='upload') # ####################################################### if settings.UPLOAD_FOLDER: - @action('download/') + + @action("download/") @action.uses(db) def download(filename): return downloader(db, settings.UPLOAD_FOLDER, filename) + # To take advantage of this in Form(s) # for every field of type upload you MUST specify: # @@ -194,17 +190,15 @@ def download(filename): # field.download_url = lambda filename: URL('download/%s' % filename) # ####################################################### -# Optionally configure celery +# Define and optionally start the scheduler # ####################################################### -if settings.USE_CELERY: - from celery import Celery - - # to use "from .common import scheduler" and then use it according - # to celery docs, examples in tasks.py - scheduler = Celery( - "apps.%s.tasks" % settings.APP_NAME, broker=settings.CELERY_BROKER +if settings.USE_SCHEDULER: + scheduler = Scheduler( + db, logger=logger, max_concurrent_runs=settings.SCHEDULER_MAX_CONCURRENT_RUNS ) - + scheduler.start() +else: + scheduler = None # ####################################################### # Enable authentication @@ -213,6 +207,8 @@ def download(filename): # ####################################################### # Define convenience decorators +# They can be used instead of @action and @action.uses +# They should NEVER BE MIXED with @action and @action.uses # ####################################################### unauthenticated = ActionFactory(db, session, T, flash, auth) authenticated = ActionFactory(db, session, T, flash, auth.user) diff --git a/apps/_scaffold/controllers.py b/apps/_scaffold/controllers.py index 05e2d09a4..dafe2b793 100644 --- a/apps/_scaffold/controllers.py +++ b/apps/_scaffold/controllers.py @@ -27,7 +27,17 @@ from py4web import action, request, abort, redirect, URL from yatl.helpers import A -from .common import db, session, T, cache, auth, logger, authenticated, unauthenticated, flash +from .common import ( + db, + session, + T, + cache, + auth, + logger, + authenticated, + unauthenticated, + flash, +) @action("index") @@ -35,5 +45,4 @@ def index(): user = auth.get_user() message = T("Hello {first_name}").format(**user) if user else T("Hello") - actions = {"allowed_actions": auth.param.allowed_actions} - return dict(message=message, actions=actions) + return dict(message=message) diff --git a/apps/_scaffold/settings.py b/apps/_scaffold/settings.py index 77bb95a4e..0f81b1bf7 100644 --- a/apps/_scaffold/settings.py +++ b/apps/_scaffold/settings.py @@ -8,16 +8,20 @@ import os from py4web.core import required_folder +# mode (default or development) +MODE = os.environ.get("PY4WEB_MODE") + # db settings APP_FOLDER = os.path.dirname(__file__) APP_NAME = os.path.split(APP_FOLDER)[-1] + # DB_FOLDER: Sets the place where migration files will be created # and is the store location for SQLite databases DB_FOLDER = required_folder(APP_FOLDER, "databases") DB_URI = "sqlite://storage.db" DB_POOL_SIZE = 1 DB_MIGRATE = True -DB_FAKE_MIGRATE = False # maybe? +DB_FAKE_MIGRATE = False # location where static files are stored: STATIC_FOLDER = required_folder(APP_FOLDER, "static") @@ -26,7 +30,10 @@ UPLOAD_FOLDER = required_folder(APP_FOLDER, "uploads") # send verification email on registration -VERIFY_EMAIL = True +VERIFY_EMAIL = MODE != "development" + +# complexity of the password 0: no constraints, 50: safe! +PASSWORD_ENTROPY = 0 if MODE == "development" else 50 # account requires to be approved ? REQUIRES_APPROVAL = False @@ -52,14 +59,14 @@ # session settings SESSION_TYPE = "cookies" -SESSION_SECRET_KEY = None # or replace with your own secret +SESSION_SECRET_KEY = None # or replace with your own secret MEMCACHE_CLIENTS = ["127.0.0.1:11211"] REDIS_SERVER = "localhost:6379" # logger settings LOGGERS = [ "warning:stdout" -] # syntax "severity:filename" filename can be stderr or stdout +] # syntax "severity:filename:format" filename can be stderr or stdout # Disable default login when using OAuth DEFAULT_LOGIN_ENABLED = True @@ -93,14 +100,18 @@ USE_LDAP = False LDAP_SETTINGS = { "mode": "ad", # Microsoft Active Directory - "server": "mydc.domain.com", # FQDN or IP of one Domain Controller - "base_dn": "cn=Users,dc=domain,dc=com", # base dn, i.e. where the users are located + "server": "mydc.domain.com", # FQDN or IP of one Domain Controller + "base_dn": "cn=Users,dc=domain,dc=com", # base dn, i.e. where the users are located } # i18n settings T_FOLDER = required_folder(APP_FOLDER, "translations") -# Celery settings +# Scheduler settings +USE_SCHEDULER = False +SCHEDULER_MAX_CONCURRENT_RUNS = 1 + +# Celery settings (alternative to the build-in scheduler) USE_CELERY = False CELERY_BROKER = "redis://localhost:6379/0" diff --git a/apps/_scaffold/static/js/utils.js b/apps/_scaffold/static/js/utils.js index c0ba784d2..aa2bd424c 100644 --- a/apps/_scaffold/static/js/utils.js +++ b/apps/_scaffold/static/js/utils.js @@ -43,12 +43,18 @@ Q.ajax = function(method, url, data, headers) { return new Promise(function(resolve, reject) { fetch(url, options).then(function(res){ res.text().then(function(body){ - res.data = body; + res.data = body; res.json = function(){return JSON.parse(body);}; resolve(res); }, reject);}).catch(reject); }); } + +Q.get = (url, headers) => Q.ajax("GET", url, null, headers); +Q.post = (url, data, headers) => Q.ajax("POST", url, data, headers); +Q.put = (url, data, headers) => Q.ajax("PUT", url, data, headers); +Q.delete = (url, headers) => Q.ajax("DELETE", url, null, headers); + // Gets a cookie value Q.get_cookie = function (name) { var cookie = RegExp("" + name + "[^;]+").exec(document.cookie); diff --git a/apps/_scaffold/tasks.py b/apps/_scaffold/tasks.py index 441839bdc..f061fdebb 100644 --- a/apps/_scaffold/tasks.py +++ b/apps/_scaffold/tasks.py @@ -1,34 +1,73 @@ -""" -To use celery tasks: -1) pip install -U "celery[redis]" -2) In settings.py: - USE_CELERY = True - CELERY_BROKER = "redis://localhost:6379/0" -3) Start "redis-server" -4) Start "celery -A apps.{appname}.tasks beat" -5) Start "celery -A apps.{appname}.tasks worker --loglevel=info" for each worker - -""" -from .common import settings, scheduler, db, Field - -# example of task that needs db access -@scheduler.task -def my_task(): +from .common import settings, scheduler +from .models import db + +# ####################################################### +# Use the built-in scheduler (nothing to install) +# ####################################################### + + +# define your tasks (or import them from other file) +def my_task(**inputs): + print(f"task running with {inputs}") try: - # this task will be executed in its own thread, connect to db - db._adapter.reconnect() # do something here db.commit() except: # rollback on failure db.rollback() + return {} + + +if settings.USE_SCHEDULER: + # register your tasks with the scheduler + scheduler.register_task("my_task", my_task) + # enqueue runs (here or in actions) for example + if db(db.task_run).count() < 1: + scheduler.enqueue_run("my_task", inputs={}, timeout=2, period=10) + +# manage your tasks via dashboard or Grid(path, db.task_run) + +# ####################################################### +# Optionally configure Celery +# ####################################################### +elif settings.USE_CELERY: + # ####################################################### + # To use celery tasks: + # 1) pip install -U "celery[redis]" + # 2) In settings.py: + # USE_CELERY = True + # CELERY_BROKER = "redis://localhost:6379/0" + # 3) Start "redis-server" + # 4) Start "celery -A apps.{appname}.tasks beat" + # 5) Start "celery -A apps.{appname}.tasks worker --loglevel=info" for each worker + # ####################################################### + + from celery import Celery + + # to use "from .common import scheduler" and then use it according + # to celery docs, examples in tasks.py + celery_scheduler = Celery( + "apps.%s.tasks" % settings.APP_NAME, broker=settings.CELERY_BROKER + ) + + # register your tasks + @scheduler.task + def my_task(): + # reconnect to database + db._adapter.reconnect() + try: + # do something here + db.commit() + except: + # rollback on failure + db.rollback() -# run my_task every 10 seconds -scheduler.conf.beat_schedule = { - "my_first_task": { - "task": "apps.%s.tasks.my_task" % settings.APP_NAME, - "schedule": 10.0, - "args": (), - }, -} + # run my_task every 10 seconds + celery_scheduler.conf.beat_schedule = { + "my_first_task": { + "task": f"apps.{settings.APP_NAME}.tasks.my_task", + "schedule": 10.0, + "args": (), + }, + } diff --git a/apps/_scaffold/templates/layout.html b/apps/_scaffold/templates/layout.html index 3d1ae83af..0edb16f51 100644 --- a/apps/_scaffold/templates/layout.html +++ b/apps/_scaffold/templates/layout.html @@ -6,7 +6,10 @@ - + [[block page_head]][[end]] @@ -31,9 +34,7 @@ diff --git a/apps/_websocket/templates/index.html b/apps/_websocket/templates/index.html index e81928396..2295f1a59 100644 --- a/apps/_websocket/templates/index.html +++ b/apps/_websocket/templates/index.html @@ -6,7 +6,6 @@ li { list-style: none; } -

                  Py4web Websockets with ssl! diff --git a/apps/fadebook/common.py b/apps/fadebook/common.py index ef28b7259..428f7499c 100644 --- a/apps/fadebook/common.py +++ b/apps/fadebook/common.py @@ -4,31 +4,20 @@ """ import os import sys -import logging from py4web import Session, Cache, Translator, Flash, DAL, Field, action +from py4web.server_adapters.logging_utils import make_logger from py4web.utils.mailer import Mailer from py4web.utils.auth import Auth from py4web.utils.downloader import downloader from pydal.tools.tags import Tags +from pydal.tools.scheduler import Scheduler from py4web.utils.factories import ActionFactory from . import settings # ####################################################### # implement custom loggers form settings.LOGGERS # ####################################################### -logger = logging.getLogger("py4web:" + settings.APP_NAME) -formatter = logging.Formatter( - "%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s" -) -for item in settings.LOGGERS: - level, filename = item.split(":", 1) - if filename in ("stdout", "stderr"): - handler = logging.StreamHandler(getattr(sys, filename)) - else: - handler = logging.FileHandler(filename) - handler.setFormatter(formatter) - logger.setLevel(getattr(logging, level.upper(), "DEBUG")) - logger.addHandler(handler) +logger = make_logger("py4web:" + settings.APP_NAME, settings.LOGGERS) # ####################################################### # connect to db @@ -52,6 +41,7 @@ # ####################################################### if settings.SESSION_TYPE == "cookies": session = Session(secret=settings.SESSION_SECRET_KEY) + elif settings.SESSION_TYPE == "redis": import redis @@ -64,11 +54,13 @@ else cs(k, v, e) ) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) + elif settings.SESSION_TYPE == "memcache": import memcache, time conn = memcache.Client(settings.MEMCACHE_CLIENTS, debug=0) session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) + elif settings.SESSION_TYPE == "database": from py4web.utils.dbstore import DBStore @@ -84,7 +76,7 @@ auth.param.login_after_registration = settings.LOGIN_AFTER_REGISTRATION auth.param.allowed_actions = settings.ALLOWED_ACTIONS auth.param.login_expiration_time = 3600 -auth.param.password_complexity = {"entropy": 0} +auth.param.password_complexity = {"entropy": settings.PASSWORD_ENTROPY} auth.param.block_previous_password_num = 3 auth.param.default_login_enabled = settings.DEFAULT_LOGIN_ENABLED auth.define_tables() @@ -133,6 +125,31 @@ callback_url="auth/plugin/oauth2google/callback", ) ) + +if settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE: + from py4web.utils.auth_plugins.oauth2google_scoped import ( + OAuth2GoogleScoped, + ) # TESTED + + auth.register_plugin( + OAuth2GoogleScoped( + secrets_file=settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE, + scopes=[], # Put here any scopes you want in addition to login + db=db, # Needed to store credentials in auth_credentials + ) + ) + +if settings.OAUTH2GITHUB_CLIENT_ID: + from py4web.utils.auth_plugins.oauth2github import OAuth2Github # TESTED + + auth.register_plugin( + OAuth2Github( + client_id=settings.OAUTH2GITHUB_CLIENT_ID, + client_secret=settings.OAUTH2GITHUB_CLIENT_SECRET, + callback_url="auth/plugin/oauth2github/callback", + ) + ) + if settings.OAUTH2FACEBOOK_CLIENT_ID: from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED @@ -160,10 +177,12 @@ # files uploaded and reference by Field(type='upload') # ####################################################### if settings.UPLOAD_FOLDER: - @action('download/') - @action.uses(db) + + @action("download/") + @action.uses(db) def download(filename): - return downloader(db, settings.UPLOAD_FOLDER, filename) + return downloader(db, settings.UPLOAD_FOLDER, filename) + # To take advantage of this in Form(s) # for every field of type upload you MUST specify: # @@ -171,17 +190,15 @@ def download(filename): # field.download_url = lambda filename: URL('download/%s' % filename) # ####################################################### -# Optionally configure celery +# Define and optionally start the scheduler # ####################################################### -if settings.USE_CELERY: - from celery import Celery - - # to use "from .common import scheduler" and then use it according - # to celery docs, examples in tasks.py - scheduler = Celery( - "apps.%s.tasks" % settings.APP_NAME, broker=settings.CELERY_BROKER +if settings.USE_SCHEDULER: + scheduler = Scheduler( + db, logger=logger, max_concurrent_runs=settings.SCHEDULER_MAX_CONCURRENT_RUNS ) - + scheduler.start() +else: + scheduler = None # ####################################################### # Enable authentication @@ -190,6 +207,8 @@ def download(filename): # ####################################################### # Define convenience decorators +# They can be used instead of @action and @action.uses +# They should NEVER BE MIXED with @action and @action.uses # ####################################################### unauthenticated = ActionFactory(db, session, T, flash, auth) authenticated = ActionFactory(db, session, T, flash, auth.user) diff --git a/apps/fadebook/controllers.py b/apps/fadebook/controllers.py index b3905e386..e6e09f480 100644 --- a/apps/fadebook/controllers.py +++ b/apps/fadebook/controllers.py @@ -7,14 +7,12 @@ # Convenience functions # -authenticated_api = action.uses(session, db, auth.user) - def check_liked(items): """add a liked attributed to each item""" query = db.item_like.created_by == auth.user_id - query &= db.item_like.item_id.belongs(items.as_dict().keys()) - liked_ids = [row.item_id for row in db(query).select(db.item_like.item_id)] + query &= db.item_like.item_id.belongs([item.id for item in items]) + liked_ids = set(row.item_id for row in db(query).select(db.item_like.item_id)) for item in items: item["liked"] = "true" if item.id in liked_ids else "false" @@ -27,8 +25,8 @@ def friend_ids(user_id): ) rows = db(query).select() return ( - set([user_id]) - |set(row.from_user for row in rows) + set([user_id]) + | set(row.from_user for row in rows) | set(row.to_user for row in rows) ) @@ -116,18 +114,18 @@ def friends(): @action("like/", method=["POST"]) -@authenticated_api +@action.uses(auth.user) def like(item_id): # try unlike if db(db.item_like.item_id == item_id).delete(): - return dict(liked="false") + return dict(liked=False) # else like db.item_like.insert(item_id=item_id) - return dict(liked="true") + return dict(liked=True) @action("friendship/request/", method=["POST"]) -@authenticated_api +@action.uses(auth.user) def friendship_request(user_id): # if request does not exist already, create it query = (db.friend_request.to_user == user_id) & ( @@ -141,8 +139,9 @@ def friendship_request(user_id): from_user=auth.user_id, to_user=user_id, status="pending" ) + @action("friendship//accept", method=["POST"]) -@authenticated_api +@action.uses(auth.user) def friendship_accept(id): # the target user can accept the request db( @@ -152,7 +151,7 @@ def friendship_accept(id): # make a button factory to reject frindship @action("friendship//reject", method=["POST"]) -@authenticated_api +@action.uses(auth.user) def friendship_reject(id): # both origin and target users can delete a request db(db.friend_request.id == id).delete() diff --git a/apps/fadebook/make_up_data.py b/apps/fadebook/make_up_data.py index c8de80e6e..a0c40b76c 100644 --- a/apps/fadebook/make_up_data.py +++ b/apps/fadebook/make_up_data.py @@ -8,7 +8,7 @@ def make(): if db(db.auth_user).count() == 1: populate(db.auth_user, 10, contents={"is_active": True}) - populate(db.feed_item, 100, contents={"is_active": True, "parent_id": 0}) + populate(db.feed_item, 100, contents={"is_active": True}) # populate(db.item_like, 1000, contents={"is_active": True}) ids = [r.id for r in db(db.auth_user).select() if r.id > 1] for k in ids[:3]: diff --git a/apps/fadebook/models.py b/apps/fadebook/models.py index f18379283..09eb7c1ba 100644 --- a/apps/fadebook/models.py +++ b/apps/fadebook/models.py @@ -2,22 +2,16 @@ from pydal.validators import IS_NOT_EMPTY db.define_table( - "feed_item", - Field("body", "text", requires=IS_NOT_EMPTY()), - auth.signature + "feed_item", Field("body", "text", requires=IS_NOT_EMPTY()), auth.signature ) -db.define_table( - "item_like", - Field("item_id", "reference feed_item"), - auth.signature -) +db.define_table("item_like", Field("item_id", "reference feed_item"), auth.signature) db.define_table( "friend_request", Field("from_user", "reference auth_user"), Field("to_user", "reference auth_user"), - Field("status", options=("accepted", "pending")), + Field("status", options=("accepted", "pending", "rejected")), ) db.commit() diff --git a/apps/fadebook/settings.py b/apps/fadebook/settings.py index 34e90c65d..c881050fe 100644 --- a/apps/fadebook/settings.py +++ b/apps/fadebook/settings.py @@ -8,16 +8,20 @@ import os from py4web.core import required_folder +# mode (default or development) +MODE = "development" + # db settings APP_FOLDER = os.path.dirname(__file__) APP_NAME = os.path.split(APP_FOLDER)[-1] + # DB_FOLDER: Sets the place where migration files will be created # and is the store location for SQLite databases DB_FOLDER = required_folder(APP_FOLDER, "databases") DB_URI = "sqlite://storage.db" DB_POOL_SIZE = 1 DB_MIGRATE = True -DB_FAKE_MIGRATE = False # maybe? +DB_FAKE_MIGRATE = False # location where static files are stored: STATIC_FOLDER = required_folder(APP_FOLDER, "static") @@ -26,17 +30,20 @@ UPLOAD_FOLDER = required_folder(APP_FOLDER, "uploads") # send verification email on registration -VERIFY_EMAIL = False +VERIFY_EMAIL = MODE != "development" + +# complexity of the password 0: no constraints, 50: safe! +PASSWORD_ENTROPY = 0 if MODE == "development" else 50 # account requires to be approved ? REQUIRES_APPROVAL = False # auto login after registration -# requires False VERIFY_EMAIL & REQUIRES_APPROVAL -LOGIN_AFTER_REGISTRATION = True +# requires False VERIFY_EMAIL & REQUIRES_APPROVAL +LOGIN_AFTER_REGISTRATION = False # ALLOWED_ACTIONS in API / default Forms: -# ["all"] +# ["all"] # ["login", "logout", "request_reset_password", "reset_password", \ # "change_password", "change_email", "profile", "config", "register", # "verify_email", "unsubscribe"] @@ -52,14 +59,14 @@ # session settings SESSION_TYPE = "cookies" -SESSION_SECRET_KEY = None # or replace with your own secret +SESSION_SECRET_KEY = None # or replace with your own secret MEMCACHE_CLIENTS = ["127.0.0.1:11211"] REDIS_SERVER = "localhost:6379" # logger settings LOGGERS = [ "warning:stdout" -] # syntax "severity:filename" filename can be stderr or stdout +] # syntax "severity:filename:format" filename can be stderr or stdout # Disable default login when using OAuth DEFAULT_LOGIN_ENABLED = True @@ -68,6 +75,10 @@ OAUTH2GOOGLE_CLIENT_ID = None OAUTH2GOOGLE_CLIENT_SECRET = None +# Single sign on Google, with stored credentials for scopes (will be used if provided). +# set it to something like os.path.join(APP_FOLDER, "private/credentials.json" +OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE = None + # single sign on Okta (will be used if provided. Please also add your tenant # name to py4web/utils/auth_plugins/oauth2okta.py. You can replace the XXX # instances with your tenant name.) @@ -78,6 +89,10 @@ OAUTH2FACEBOOK_CLIENT_ID = None OAUTH2FACEBOOK_CLIENT_SECRET = None +# single sign on GitHub (will be used if provided) +OAUTH2GITHUB_CLIENT_ID = None +OAUTH2GITHUB_CLIENT_SECRET = None + # enable PAM USE_PAM = False @@ -85,14 +100,18 @@ USE_LDAP = False LDAP_SETTINGS = { "mode": "ad", # Microsoft Active Directory - "server": "mydc.domain.com", # FQDN or IP of one Domain Controller - "base_dn": "cn=Users,dc=domain,dc=com", # base dn, i.e. where the users are located + "server": "mydc.domain.com", # FQDN or IP of one Domain Controller + "base_dn": "cn=Users,dc=domain,dc=com", # base dn, i.e. where the users are located } # i18n settings T_FOLDER = required_folder(APP_FOLDER, "translations") -# Celery settings +# Scheduler settings +USE_SCHEDULER = False +SCHEDULER_MAX_CONCURRENT_RUNS = 1 + +# Celery settings (alternative to the build-in scheduler) USE_CELERY = False CELERY_BROKER = "redis://localhost:6379/0" diff --git a/apps/fadebook/static/js/axios.min.js b/apps/fadebook/static/js/axios.min.js deleted file mode 100644 index 2d030546a..000000000 --- a/apps/fadebook/static/js/axios.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* axios v0.20.0 | (c) 2020 by Matt Zabriskie */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(4),a=n(22),u=n(10),c=r(u);c.Axios=s,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function s(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){u.headers[e]={}}),i.forEach(["post","put","patch"],function(e){u.headers[e]=i.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),i=n(16),s=n(5),a=n(17),u=n(20),c=n(21),f=n(14);e.exports=function(e){return new Promise(function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"],(r.isBlob(p)||r.isFile(p))&&p.type&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=unescape(encodeURIComponent(e.auth.password))||"";d.Authorization="Basic "+btoa(h+":"+m)}var y=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),s(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?u(l.getAllResponseHeaders()):null,i=e.responseType&&"text"!==e.responseType?l.response:l.responseText,s={data:i,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,s),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=(e.withCredentials||c(y))&&e.xsrfCookieName?i.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),n(e),l=null)}),p||(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),s===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(18),o=n(19);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){function n(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function o(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(e[o],t[o])}t=t||{};var i={},s=["url","method","data"],a=["headers","auth","proxy","params"],u=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],c=["validateStatus"];r.forEach(s,function(e){r.isUndefined(t[e])||(i[e]=n(void 0,t[e]))}),r.forEach(a,o),r.forEach(u,function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(void 0,t[o])}),r.forEach(c,function(r){r in t?i[r]=n(e[r],t[r]):r in e&&(i[r]=n(void 0,e[r]))});var f=s.concat(a).concat(u).concat(c),p=Object.keys(e).concat(Object.keys(t)).filter(function(e){return f.indexOf(e)===-1});return r.forEach(p,o),i}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); -//# sourceMappingURL=axios.min.map \ No newline at end of file diff --git a/apps/fadebook/static/js/utils.js b/apps/fadebook/static/js/utils.js index c0ba784d2..aa2bd424c 100644 --- a/apps/fadebook/static/js/utils.js +++ b/apps/fadebook/static/js/utils.js @@ -43,12 +43,18 @@ Q.ajax = function(method, url, data, headers) { return new Promise(function(resolve, reject) { fetch(url, options).then(function(res){ res.text().then(function(body){ - res.data = body; + res.data = body; res.json = function(){return JSON.parse(body);}; resolve(res); }, reject);}).catch(reject); }); } + +Q.get = (url, headers) => Q.ajax("GET", url, null, headers); +Q.post = (url, data, headers) => Q.ajax("POST", url, data, headers); +Q.put = (url, data, headers) => Q.ajax("PUT", url, data, headers); +Q.delete = (url, headers) => Q.ajax("DELETE", url, null, headers); + // Gets a cookie value Q.get_cookie = function (name) { var cookie = RegExp("" + name + "[^;]+").exec(document.cookie); diff --git a/apps/fadebook/templates/friends.html b/apps/fadebook/templates/friends.html index 73389400d..dc18bc412 100644 --- a/apps/fadebook/templates/friends.html +++ b/apps/fadebook/templates/friends.html @@ -10,7 +10,7 @@

                  Friends Search

            • - + [[pass]]

              Type

              [[=user.first_name]] [[=user.last_name]] [[=user.username]]
              @@ -25,12 +25,12 @@

              Requests received

              [[=r.friend_request.status]] [[if r.friend_request.status == 'pending':]] - - [[else:]] - [[pass]] @@ -47,7 +47,7 @@

              Requests sent

              [[=r.auth_user.username]] [[=r.friend_request.status]] - @@ -56,6 +56,9 @@

              Requests sent

              diff --git a/apps/fadebook/templates/layout.html b/apps/fadebook/templates/layout.html index c4f9ef902..b1ee56a60 100644 --- a/apps/fadebook/templates/layout.html +++ b/apps/fadebook/templates/layout.html @@ -5,12 +5,12 @@ - + [[block page_head]][[end]] @@ -77,6 +77,5 @@ - [[block page_scripts]][[end]] diff --git a/apps/fadebook/templates/posts.html b/apps/fadebook/templates/posts.html index 9617a24dc..9a2e334f0 100644 --- a/apps/fadebook/templates/posts.html +++ b/apps/fadebook/templates/posts.html @@ -6,14 +6,15 @@ on [[=item.created_on]] says
              [[=item.body]]
              - +

              [[pass]] diff --git a/apps/showcase/__init__.py b/apps/showcase/__init__.py index 5308657a9..9c3999dd0 100644 --- a/apps/showcase/__init__.py +++ b/apps/showcase/__init__.py @@ -62,20 +62,21 @@ def show(name): path = name name = name.rstrip("/0123456789") data = [] - filename = f"apps/showcase/{name}.md" + here = os.path.dirname(__file__) + filename = f"{here}/{name}.md" if os.path.exists(filename): with open(filename) as stream: metadata = stream.read().strip() data.append({"name": f"{name}.md", "content": metadata, "language": "markdown"}) - filename = f"apps/showcase/{name}.py" + filename = f"{here}/{name}.py" if not os.path.exists(filename): raise HTTP(404) with open(filename) as stream: controller = stream.read().strip() data.append({"name": f"{name}.py", "content": controller, "language": "python"}) - templates = re.compile("[/\w]+\.html").findall(controller) + templates = re.compile(r"[/\w]+\.html").findall(controller) for template in templates: - with open(f"apps/showcase/templates/{template}") as stream: + with open(f"{here}/templates/{template}") as stream: content = stream.read().strip() data.append( { @@ -89,10 +90,10 @@ def show(name): if not other.startswith("."): continue filename = other[1:].replace(".", "/") + ".py" - with open(f"apps/showcase/{filename}") as stream: + with open(f"{here}/{filename}") as stream: content = stream.read().strip() data.append({"shortname": filename, "content": content, "language": "python"}) # drop the subfolder name path = "/".join(path.split("/")[1:]) - executable = MODE == "full" or name in globals() + executable = MODE == "full" or name.split("/")[-1] in globals() return {"files": data, "mode": MODE, "path": path, "executable": executable} diff --git a/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_auth_user.table b/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_auth_user.table deleted file mode 100644 index e323d5b6a..000000000 Binary files a/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_auth_user.table and /dev/null differ diff --git a/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_auth_user_tag_groups.table b/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_auth_user_tag_groups.table deleted file mode 100644 index f965a9dfd..000000000 Binary files a/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_auth_user_tag_groups.table and /dev/null differ diff --git a/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_vue_form_table.table b/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_vue_form_table.table deleted file mode 100644 index 8b548a456..000000000 Binary files a/apps/showcase/examples/databases/fb87181b96a99be45f5a23f4277867ce_vue_form_table.table and /dev/null differ diff --git a/apps/showcase/examples/databases/sql.log b/apps/showcase/examples/databases/sql.log deleted file mode 100644 index 0c59cdd42..000000000 --- a/apps/showcase/examples/databases/sql.log +++ /dev/null @@ -1,32 +0,0 @@ -timestamp: 2022-06-05T14:36:03.545815 -CREATE TABLE "auth_user"( - "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "username" CHAR(512) UNIQUE, - "email" CHAR(512) UNIQUE, - "password" CHAR(512), - "first_name" CHAR(512), - "last_name" CHAR(512), - "sso_id" CHAR(512), - "action_token" CHAR(512), - "last_password_change" TIMESTAMP -); -success! -timestamp: 2022-06-05T14:36:03.553337 -CREATE TABLE "auth_user_tag_groups"( - "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "tagpath" CHAR(512), - "record_id" INTEGER REFERENCES "auth_user" ("id") ON DELETE CASCADE -); -success! -timestamp: 2022-06-05T14:51:31.703785 -CREATE TABLE "vue_form_table"( - "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "first_name" CHAR(512), - "last_name" CHAR(512), - "read" CHAR(1), - "animal" CHAR(512), - "arrival_time" TIMESTAMP, - "date_of_birth" DATE, - "narrative" TEXT -); -success! diff --git a/apps/showcase/examples/databases/storage.db b/apps/showcase/examples/databases/storage.db deleted file mode 100644 index 3bb960460..000000000 Binary files a/apps/showcase/examples/databases/storage.db and /dev/null differ diff --git a/apps/showcase/examples/example_html_grid.py b/apps/showcase/examples/example_html_grid.py index 45345497b..52000459d 100644 --- a/apps/showcase/examples/example_html_grid.py +++ b/apps/showcase/examples/example_html_grid.py @@ -50,8 +50,8 @@ def example_html_grid(path=None): **grid_param ) - grid.formatters["thing.color"] = lambda color: I( - _class="fa fa-circle", _style="color:" + color + grid.columns[3].represent = lambda row: I( + _class="fa fa-circle", _style="color:" + row.color ) return dict(grid=grid) diff --git a/apps/showcase/examples/settings.py b/apps/showcase/examples/settings.py index 6b07fc170..deb0e3285 100644 --- a/apps/showcase/examples/settings.py +++ b/apps/showcase/examples/settings.py @@ -9,7 +9,7 @@ MODE = os.environ.get("PY4WEB_DASHBOARD_MODE", "none") # db settings -APP_FOLDER = os.path.join(os.path.dirname(__file__), "..") +APP_FOLDER = os.path.dirname(os.path.dirname(__file__)) APP_NAME = os.path.split(APP_FOLDER)[-1] # DB_FOLDER: Sets the place where migration files will be created # and is the store location for SQLite databases diff --git a/apps/showcase/static/components-bulma/mtable.js b/apps/showcase/static/components-bulma/mtable.js index c75b14d3c..7a42bb508 100644 --- a/apps/showcase/static/components-bulma/mtable.js +++ b/apps/showcase/static/components-bulma/mtable.js @@ -2,7 +2,7 @@ var mtable = { props: ['url', 'filter', 'order', 'editable', 'create', 'deletable', 'render'], data: null, methods: {}}; - mtable.data = function() { + mtable.data = function() { var data = {url: this.url, busy: false, filter: this.filter || '', @@ -52,10 +52,10 @@ if (filters.length) url += '&'+filters.join('&'); if (self.order) url += '&@order='+self.order; self.busy = true; - axios.get(url).then(function (res) { + Q.get(url).then(function (res) { self.busy = false; - if(!length) self.table = res.data; - else self.table.items = self.table.items.concat(res.data.items); + if(!length) self.table = res.json(); + else self.table.items = self.table.items.concat(res.json().items); }); }; @@ -100,8 +100,8 @@ reference_table_url.pop() reference_table_url.push(field.references) reference_table_url = reference_table_url.join('/') + '?@options_list=true'; - axios.get(reference_table_url).then(function (res) { - let url_components = res.config.url.split('?')[0].split('/'); + Q.get(reference_table_url).then(function (res) { + let url_components = res.json().config.url.split('?')[0].split('/'); self.reference_options[url_components[url_components.length - 1 ]] = res.data.items; }); @@ -129,7 +129,7 @@ if (window.confirm("Really delete record?")) { let url = this.url + '/' + item.id; this.table.items = this.table.items.filter((i)=>{return i.id != item.id;}); - axios.delete(url); + Q.delete(url); if (item==this.item) this.item = null; } }; @@ -150,27 +150,30 @@ } if (item.id) { url += '/' + item.id; - axios.put(url, item).then(mtable.handle_response('put', this), - mtable.handle_response('put', this)); + var data = JSON.parse(JSON.stringify(item)); + delete data["id"]; + Q.put(url, data).then(mtable.handle_response('put', this), + mtable.handle_response('put', this)); } else { - axios.post(url, item).then(mtable.handle_response('post', this), - mtable.handle_response('post', this)); - } + Q.post(url, item).then(mtable.handle_response('post', this), + mtable.handle_response('post', this)); + } }; mtable.handle_response = function(method, data) { self.busy = false; return function(res) { - if (res.response) res = res.response; // deal with error weirdness + res = res.json(); if (method == 'post') { data.table.items = []; mtable.methods.load.call(data); } - if (res.data.status == 'success') { + if (res.status == 'success') { data.clear(); + location.reload(); } else { - data.errors = res.data.errors; - data.message = res.data.message; + data.errors = res.errors; + data.message = res.message; } }; }; diff --git a/apps/showcase/static/components/mtable.js b/apps/showcase/static/components/mtable.js index c75b14d3c..7a42bb508 100644 --- a/apps/showcase/static/components/mtable.js +++ b/apps/showcase/static/components/mtable.js @@ -2,7 +2,7 @@ var mtable = { props: ['url', 'filter', 'order', 'editable', 'create', 'deletable', 'render'], data: null, methods: {}}; - mtable.data = function() { + mtable.data = function() { var data = {url: this.url, busy: false, filter: this.filter || '', @@ -52,10 +52,10 @@ if (filters.length) url += '&'+filters.join('&'); if (self.order) url += '&@order='+self.order; self.busy = true; - axios.get(url).then(function (res) { + Q.get(url).then(function (res) { self.busy = false; - if(!length) self.table = res.data; - else self.table.items = self.table.items.concat(res.data.items); + if(!length) self.table = res.json(); + else self.table.items = self.table.items.concat(res.json().items); }); }; @@ -100,8 +100,8 @@ reference_table_url.pop() reference_table_url.push(field.references) reference_table_url = reference_table_url.join('/') + '?@options_list=true'; - axios.get(reference_table_url).then(function (res) { - let url_components = res.config.url.split('?')[0].split('/'); + Q.get(reference_table_url).then(function (res) { + let url_components = res.json().config.url.split('?')[0].split('/'); self.reference_options[url_components[url_components.length - 1 ]] = res.data.items; }); @@ -129,7 +129,7 @@ if (window.confirm("Really delete record?")) { let url = this.url + '/' + item.id; this.table.items = this.table.items.filter((i)=>{return i.id != item.id;}); - axios.delete(url); + Q.delete(url); if (item==this.item) this.item = null; } }; @@ -150,27 +150,30 @@ } if (item.id) { url += '/' + item.id; - axios.put(url, item).then(mtable.handle_response('put', this), - mtable.handle_response('put', this)); + var data = JSON.parse(JSON.stringify(item)); + delete data["id"]; + Q.put(url, data).then(mtable.handle_response('put', this), + mtable.handle_response('put', this)); } else { - axios.post(url, item).then(mtable.handle_response('post', this), - mtable.handle_response('post', this)); - } + Q.post(url, item).then(mtable.handle_response('post', this), + mtable.handle_response('post', this)); + } }; mtable.handle_response = function(method, data) { self.busy = false; return function(res) { - if (res.response) res = res.response; // deal with error weirdness + res = res.json(); if (method == 'post') { data.table.items = []; mtable.methods.load.call(data); } - if (res.data.status == 'success') { + if (res.status == 'success') { data.clear(); + location.reload(); } else { - data.errors = res.data.errors; - data.message = res.data.message; + data.errors = res.errors; + data.message = res.message; } }; }; diff --git a/apps/showcase/static/css/no.css b/apps/showcase/static/css/no.css index d1230fb0f..cfe5f519e 100644 --- a/apps/showcase/static/css/no.css +++ b/apps/showcase/static/css/no.css @@ -115,7 +115,7 @@ header, footer { code { background: #f4f5f6; border-radius: .4rem; - font-size: 90; + font-size: 0.8em; margin: 0 .2rem; padding: .2rem .5rem; white-space: nowrap; @@ -540,4 +540,4 @@ ul.tags-list li { ul.tags-list li[data-selected=true] { opacity: 1.0; -} \ No newline at end of file +} diff --git a/apps/showcase/static/css/prism.css b/apps/showcase/static/css/prism.css index 15fa9b747..221f20cfd 100644 --- a/apps/showcase/static/css/prism.css +++ b/apps/showcase/static/css/prism.css @@ -1,3 +1,3 @@ -/* PrismJS 1.28.0 -https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+python */ -code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+python&plugins=remove-initial-line-feed+normalize-whitespace */ +code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} diff --git a/apps/showcase/static/js/axios.min.js b/apps/showcase/static/js/axios.min.js index 2d030546a..6b3d816d5 100644 --- a/apps/showcase/static/js/axios.min.js +++ b/apps/showcase/static/js/axios.min.js @@ -1,3 +1,2 @@ -/* axios v0.20.0 | (c) 2020 by Matt Zabriskie */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(4),a=n(22),u=n(10),c=r(u);c.Axios=s,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function s(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){u.headers[e]={}}),i.forEach(["post","put","patch"],function(e){u.headers[e]=i.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),i=n(16),s=n(5),a=n(17),u=n(20),c=n(21),f=n(14);e.exports=function(e){return new Promise(function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"],(r.isBlob(p)||r.isFile(p))&&p.type&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=unescape(encodeURIComponent(e.auth.password))||"";d.Authorization="Basic "+btoa(h+":"+m)}var y=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),s(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?u(l.getAllResponseHeaders()):null,i=e.responseType&&"text"!==e.responseType?l.response:l.responseText,s={data:i,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,s),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=(e.withCredentials||c(y))&&e.xsrfCookieName?i.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),n(e),l=null)}),p||(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),s===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(18),o=n(19);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){function n(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function o(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(e[o],t[o])}t=t||{};var i={},s=["url","method","data"],a=["headers","auth","proxy","params"],u=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],c=["validateStatus"];r.forEach(s,function(e){r.isUndefined(t[e])||(i[e]=n(void 0,t[e]))}),r.forEach(a,o),r.forEach(u,function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(void 0,t[o])}),r.forEach(c,function(r){r in t?i[r]=n(e[r],t[r]):r in e&&(i[r]=n(void 0,e[r]))});var f=s.concat(a).concat(u).concat(c),p=Object.keys(e).concat(Object.keys(t)).filter(function(e){return f.indexOf(e)===-1});return r.forEach(p,o),i}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); -//# sourceMappingURL=axios.min.map \ No newline at end of file +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).axios=t()}(this,(function(){"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n2&&void 0!==arguments[2]?arguments[2]:{},a=i.allOwnKeys,s=void 0!==a&&a;if(null!=t)if("object"!==e(t)&&(t=[t]),p(t))for(r=0,o=t.length;r0;)if(t===(n=r[o]).toLowerCase())return n;return null}var C="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:global,N=function(e){return!h(e)&&e!==C};var x,P=(x="undefined"!=typeof Uint8Array&&c(Uint8Array),function(e){return x&&e instanceof x}),k=l("HTMLFormElement"),U=function(e){var t=Object.prototype.hasOwnProperty;return function(e,n){return t.call(e,n)}}(),_=l("RegExp"),F=function(e,t){var n=Object.getOwnPropertyDescriptors(e),r={};T(n,(function(n,o){var i;!1!==(i=t(n,o,e))&&(r[o]=i||n)})),Object.defineProperties(e,r)},B="abcdefghijklmnopqrstuvwxyz",L="0123456789",D={DIGIT:L,ALPHA:B,ALPHA_DIGIT:B+B.toUpperCase()+L};var I=l("AsyncFunction"),q={isArray:p,isArrayBuffer:m,isBuffer:function(e){return null!==e&&!h(e)&&null!==e.constructor&&!h(e.constructor)&&y(e.constructor.isBuffer)&&e.constructor.isBuffer(e)},isFormData:function(e){var t;return e&&("function"==typeof FormData&&e instanceof FormData||y(e.append)&&("formdata"===(t=f(e))||"object"===t&&y(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&m(e.buffer)},isString:v,isNumber:b,isBoolean:function(e){return!0===e||!1===e},isObject:g,isPlainObject:w,isUndefined:h,isDate:E,isFile:O,isBlob:S,isRegExp:_,isFunction:y,isStream:function(e){return g(e)&&y(e.pipe)},isURLSearchParams:A,isTypedArray:P,isFileList:R,forEach:T,merge:function e(){for(var t=N(this)&&this||{},n=t.caseless,r={},o=function(t,o){var i=n&&j(r,o)||o;w(r[i])&&w(t)?r[i]=e(r[i],t):w(t)?r[i]=e({},t):p(t)?r[i]=t.slice():r[i]=t},i=0,a=arguments.length;i3&&void 0!==arguments[3]?arguments[3]:{},o=r.allOwnKeys;return T(t,(function(t,r){n&&y(t)?e[r]=a(t,n):e[r]=t}),{allOwnKeys:o}),e},trim:function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e},inherits:function(e,t,n,r){e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},toFlatObject:function(e,t,n,r){var o,i,a,s={};if(t=t||{},null==e)return t;do{for(i=(o=Object.getOwnPropertyNames(e)).length;i-- >0;)a=o[i],r&&!r(a,e,t)||s[a]||(t[a]=e[a],s[a]=!0);e=!1!==n&&c(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},kindOf:f,kindOfTest:l,endsWith:function(e,t,n){e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;var r=e.indexOf(t,n);return-1!==r&&r===n},toArray:function(e){if(!e)return null;if(p(e))return e;var t=e.length;if(!b(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},forEachEntry:function(e,t){for(var n,r=(e&&e[Symbol.iterator]).call(e);(n=r.next())&&!n.done;){var o=n.value;t.call(e,o[0],o[1])}},matchAll:function(e,t){for(var n,r=[];null!==(n=e.exec(t));)r.push(n);return r},isHTMLForm:k,hasOwnProperty:U,hasOwnProp:U,reduceDescriptors:F,freezeMethods:function(e){F(e,(function(t,n){if(y(e)&&-1!==["arguments","caller","callee"].indexOf(n))return!1;var r=e[n];y(r)&&(t.enumerable=!1,"writable"in t?t.writable=!1:t.set||(t.set=function(){throw Error("Can not rewrite read-only method '"+n+"'")}))}))},toObjectSet:function(e,t){var n={},r=function(e){e.forEach((function(e){n[e]=!0}))};return p(e)?r(e):r(String(e).split(t)),n},toCamelCase:function(e){return e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,(function(e,t,n){return t.toUpperCase()+n}))},noop:function(){},toFiniteNumber:function(e,t){return e=+e,Number.isFinite(e)?e:t},findKey:j,global:C,isContextDefined:N,ALPHABET:D,generateString:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:16,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:D.ALPHA_DIGIT,n="",r=t.length;e--;)n+=t[Math.random()*r|0];return n},isSpecCompliantForm:function(e){return!!(e&&y(e.append)&&"FormData"===e[Symbol.toStringTag]&&e[Symbol.iterator])},toJSONObject:function(e){var t=new Array(10);return function e(n,r){if(g(n)){if(t.indexOf(n)>=0)return;if(!("toJSON"in n)){t[r]=n;var o=p(n)?[]:{};return T(n,(function(t,n){var i=e(t,r+1);!h(i)&&(o[n]=i)})),t[r]=void 0,o}}return n}(e,0)},isAsyncFn:I,isThenable:function(e){return e&&(g(e)||y(e))&&y(e.then)&&y(e.catch)}};function M(e,t,n,r,o){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),o&&(this.response=o)}q.inherits(M,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:q.toJSONObject(this.config),code:this.code,status:this.response&&this.response.status?this.response.status:null}}});var z=M.prototype,H={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach((function(e){H[e]={value:e}})),Object.defineProperties(M,H),Object.defineProperty(z,"isAxiosError",{value:!0}),M.from=function(e,t,n,r,o,i){var a=Object.create(z);return q.toFlatObject(e,a,(function(e){return e!==Error.prototype}),(function(e){return"isAxiosError"!==e})),M.call(a,e.message,t,n,r,o),a.cause=e,a.name=e.name,i&&Object.assign(a,i),a};function J(e){return q.isPlainObject(e)||q.isArray(e)}function W(e){return q.endsWith(e,"[]")?e.slice(0,-2):e}function K(e,t,n){return e?e.concat(t).map((function(e,t){return e=W(e),!n&&t?"["+e+"]":e})).join(n?".":""):t}var V=q.toFlatObject(q,{},null,(function(e){return/^is[A-Z]/.test(e)}));function G(t,n,r){if(!q.isObject(t))throw new TypeError("target must be an object");n=n||new FormData;var o=(r=q.toFlatObject(r,{metaTokens:!0,dots:!1,indexes:!1},!1,(function(e,t){return!q.isUndefined(t[e])}))).metaTokens,i=r.visitor||f,a=r.dots,s=r.indexes,u=(r.Blob||"undefined"!=typeof Blob&&Blob)&&q.isSpecCompliantForm(n);if(!q.isFunction(i))throw new TypeError("visitor must be a function");function c(e){if(null===e)return"";if(q.isDate(e))return e.toISOString();if(!u&&q.isBlob(e))throw new M("Blob is not supported. Use a Buffer instead.");return q.isArrayBuffer(e)||q.isTypedArray(e)?u&&"function"==typeof Blob?new Blob([e]):Buffer.from(e):e}function f(t,r,i){var u=t;if(t&&!i&&"object"===e(t))if(q.endsWith(r,"{}"))r=o?r:r.slice(0,-2),t=JSON.stringify(t);else if(q.isArray(t)&&function(e){return q.isArray(e)&&!e.some(J)}(t)||(q.isFileList(t)||q.endsWith(r,"[]"))&&(u=q.toArray(t)))return r=W(r),u.forEach((function(e,t){!q.isUndefined(e)&&null!==e&&n.append(!0===s?K([r],t,a):null===s?r:r+"[]",c(e))})),!1;return!!J(t)||(n.append(K(i,r,a),c(t)),!1)}var l=[],d=Object.assign(V,{defaultVisitor:f,convertValue:c,isVisitable:J});if(!q.isObject(t))throw new TypeError("data must be an object");return function e(t,r){if(!q.isUndefined(t)){if(-1!==l.indexOf(t))throw Error("Circular reference detected in "+r.join("."));l.push(t),q.forEach(t,(function(t,o){!0===(!(q.isUndefined(t)||null===t)&&i.call(n,t,q.isString(o)?o.trim():o,r,d))&&e(t,r?r.concat(o):[o])})),l.pop()}}(t),n}function $(e){var t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,(function(e){return t[e]}))}function X(e,t){this._pairs=[],e&&G(e,this,t)}var Q=X.prototype;function Z(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Y(e,t,n){if(!t)return e;var r,o=n&&n.encode||Z,i=n&&n.serialize;if(r=i?i(t,n):q.isURLSearchParams(t)?t.toString():new X(t,n).toString(o)){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+r}return e}Q.append=function(e,t){this._pairs.push([e,t])},Q.toString=function(e){var t=e?function(t){return e.call(this,t,$)}:$;return this._pairs.map((function(e){return t(e[0])+"="+t(e[1])}),"").join("&")};var ee,te=function(){function e(){t(this,e),this.handlers=[]}return r(e,[{key:"use",value:function(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!n&&n.synchronous,runWhen:n?n.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(e){this.handlers[e]&&(this.handlers[e]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(e){q.forEach(this.handlers,(function(t){null!==t&&e(t)}))}}]),e}(),ne={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},re={isBrowser:!0,classes:{URLSearchParams:"undefined"!=typeof URLSearchParams?URLSearchParams:X,FormData:"undefined"!=typeof FormData?FormData:null,Blob:"undefined"!=typeof Blob?Blob:null},isStandardBrowserEnv:("undefined"==typeof navigator||"ReactNative"!==(ee=navigator.product)&&"NativeScript"!==ee&&"NS"!==ee)&&"undefined"!=typeof window&&"undefined"!=typeof document,isStandardBrowserWebWorkerEnv:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&"function"==typeof self.importScripts,protocols:["http","https","file","blob","url","data"]};function oe(e){function t(e,n,r,o){var i=e[o++],a=Number.isFinite(+i),s=o>=e.length;return i=!i&&q.isArray(r)?r.length:i,s?(q.hasOwnProp(r,i)?r[i]=[r[i],n]:r[i]=n,!a):(r[i]&&q.isObject(r[i])||(r[i]=[]),t(e,n,r[i],o)&&q.isArray(r[i])&&(r[i]=function(e){var t,n,r={},o=Object.keys(e),i=o.length;for(t=0;t-1,i=q.isObject(e);if(i&&q.isHTMLForm(e)&&(e=new FormData(e)),q.isFormData(e))return o&&o?JSON.stringify(oe(e)):e;if(q.isArrayBuffer(e)||q.isBuffer(e)||q.isStream(e)||q.isFile(e)||q.isBlob(e))return e;if(q.isArrayBufferView(e))return e.buffer;if(q.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();if(i){if(r.indexOf("application/x-www-form-urlencoded")>-1)return function(e,t){return G(e,new re.classes.URLSearchParams,Object.assign({visitor:function(e,t,n,r){return re.isNode&&q.isBuffer(e)?(this.append(t,e.toString("base64")),!1):r.defaultVisitor.apply(this,arguments)}},t))}(e,this.formSerializer).toString();if((n=q.isFileList(e))||r.indexOf("multipart/form-data")>-1){var a=this.env&&this.env.FormData;return G(n?{"files[]":e}:e,a&&new a,this.formSerializer)}}return i||o?(t.setContentType("application/json",!1),function(e,t,n){if(q.isString(e))try{return(t||JSON.parse)(e),q.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(n||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||ie.transitional,n=t&&t.forcedJSONParsing,r="json"===this.responseType;if(e&&q.isString(e)&&(n&&!this.responseType||r)){var o=!(t&&t.silentJSONParsing)&&r;try{return JSON.parse(e)}catch(e){if(o){if("SyntaxError"===e.name)throw M.from(e,M.ERR_BAD_RESPONSE,this,null,this.response);throw e}}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:re.classes.FormData,Blob:re.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};q.forEach(["delete","get","head","post","put","patch"],(function(e){ie.headers[e]={}}));var ae=ie,se=q.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),ue=Symbol("internals");function ce(e){return e&&String(e).trim().toLowerCase()}function fe(e){return!1===e||null==e?e:q.isArray(e)?e.map(fe):String(e)}function le(e,t,n,r,o){return q.isFunction(r)?r.call(this,t,n):(o&&(t=n),q.isString(t)?q.isString(r)?-1!==t.indexOf(r):q.isRegExp(r)?r.test(t):void 0:void 0)}var de=function(e,n){function i(e){t(this,i),e&&this.set(e)}return r(i,[{key:"set",value:function(e,t,n){var r=this;function o(e,t,n){var o=ce(t);if(!o)throw new Error("header name must be a non-empty string");var i=q.findKey(r,o);(!i||void 0===r[i]||!0===n||void 0===n&&!1!==r[i])&&(r[i||t]=fe(e))}var i,a,s,u,c,f=function(e,t){return q.forEach(e,(function(e,n){return o(e,n,t)}))};return q.isPlainObject(e)||e instanceof this.constructor?f(e,t):q.isString(e)&&(e=e.trim())&&!/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim())?f((c={},(i=e)&&i.split("\n").forEach((function(e){u=e.indexOf(":"),a=e.substring(0,u).trim().toLowerCase(),s=e.substring(u+1).trim(),!a||c[a]&&se[a]||("set-cookie"===a?c[a]?c[a].push(s):c[a]=[s]:c[a]=c[a]?c[a]+", "+s:s)})),c),t):null!=e&&o(t,e,n),this}},{key:"get",value:function(e,t){if(e=ce(e)){var n=q.findKey(this,e);if(n){var r=this[n];if(!t)return r;if(!0===t)return function(e){for(var t,n=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;t=r.exec(e);)n[t[1]]=t[2];return n}(r);if(q.isFunction(t))return t.call(this,r,n);if(q.isRegExp(t))return t.exec(r);throw new TypeError("parser must be boolean|regexp|function")}}}},{key:"has",value:function(e,t){if(e=ce(e)){var n=q.findKey(this,e);return!(!n||void 0===this[n]||t&&!le(0,this[n],n,t))}return!1}},{key:"delete",value:function(e,t){var n=this,r=!1;function o(e){if(e=ce(e)){var o=q.findKey(n,e);!o||t&&!le(0,n[o],o,t)||(delete n[o],r=!0)}}return q.isArray(e)?e.forEach(o):o(e),r}},{key:"clear",value:function(e){for(var t=Object.keys(this),n=t.length,r=!1;n--;){var o=t[n];e&&!le(0,this[o],o,e,!0)||(delete this[o],r=!0)}return r}},{key:"normalize",value:function(e){var t=this,n={};return q.forEach(this,(function(r,o){var i=q.findKey(n,o);if(i)return t[i]=fe(r),void delete t[o];var a=e?function(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(function(e,t,n){return t.toUpperCase()+n}))}(o):String(o).trim();a!==o&&delete t[o],t[a]=fe(r),n[a]=!0})),this}},{key:"concat",value:function(){for(var e,t=arguments.length,n=new Array(t),r=0;r1?n-1:0),o=1;o1?"since :\n"+u.map(Oe).join("\n"):" "+Oe(u[0]):"as no adapter specified"),"ERR_NOT_SUPPORT")}return n};function Ae(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new ve(null,e)}function Te(e){return Ae(e),e.headers=pe.from(e.headers),e.data=he.call(e,e.transformRequest),-1!==["post","put","patch"].indexOf(e.method)&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Re(e.adapter||ae.adapter)(e).then((function(t){return Ae(e),t.data=he.call(e,e.transformResponse,t),t.headers=pe.from(t.headers),t}),(function(t){return me(t)||(Ae(e),t&&t.response&&(t.response.data=he.call(e,e.transformResponse,t.response),t.response.headers=pe.from(t.response.headers))),Promise.reject(t)}))}var je=function(e){return e instanceof pe?e.toJSON():e};function Ce(e,t){t=t||{};var n={};function r(e,t,n){return q.isPlainObject(e)&&q.isPlainObject(t)?q.merge.call({caseless:n},e,t):q.isPlainObject(t)?q.merge({},t):q.isArray(t)?t.slice():t}function o(e,t,n){return q.isUndefined(t)?q.isUndefined(e)?void 0:r(void 0,e,n):r(e,t,n)}function i(e,t){if(!q.isUndefined(t))return r(void 0,t)}function a(e,t){return q.isUndefined(t)?q.isUndefined(e)?void 0:r(void 0,e):r(void 0,t)}function s(n,o,i){return i in t?r(n,o):i in e?r(void 0,n):void 0}var u={url:i,method:i,data:i,baseURL:a,transformRequest:a,transformResponse:a,paramsSerializer:a,timeout:a,timeoutMessage:a,withCredentials:a,adapter:a,responseType:a,xsrfCookieName:a,xsrfHeaderName:a,onUploadProgress:a,onDownloadProgress:a,decompress:a,maxContentLength:a,maxBodyLength:a,beforeRedirect:a,transport:a,httpAgent:a,httpsAgent:a,cancelToken:a,socketPath:a,responseEncoding:a,validateStatus:s,headers:function(e,t){return o(je(e),je(t),!0)}};return q.forEach(Object.keys(Object.assign({},e,t)),(function(r){var i=u[r]||o,a=i(e[r],t[r],r);q.isUndefined(a)&&i!==s||(n[r]=a)})),n}var Ne="1.5.1",xe={};["object","boolean","number","function","string","symbol"].forEach((function(t,n){xe[t]=function(r){return e(r)===t||"a"+(n<1?"n ":" ")+t}}));var Pe={};xe.transitional=function(e,t,n){function r(e,t){return"[Axios v1.5.1] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,o,i){if(!1===e)throw new M(r(o," has been removed"+(t?" in "+t:"")),M.ERR_DEPRECATED);return t&&!Pe[o]&&(Pe[o]=!0,console.warn(r(o," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,o,i)}};var ke={assertOptions:function(t,n,r){if("object"!==e(t))throw new M("options must be an object",M.ERR_BAD_OPTION_VALUE);for(var o=Object.keys(t),i=o.length;i-- >0;){var a=o[i],s=n[a];if(s){var u=t[a],c=void 0===u||s(u,a,t);if(!0!==c)throw new M("option "+a+" must be "+c,M.ERR_BAD_OPTION_VALUE)}else if(!0!==r)throw new M("Unknown option "+a,M.ERR_BAD_OPTION)}},validators:xe},Ue=ke.validators,_e=function(){function e(n){t(this,e),this.defaults=n,this.interceptors={request:new te,response:new te}}return r(e,[{key:"request",value:function(e,t){"string"==typeof e?(t=t||{}).url=e:t=e||{};var n=t=Ce(this.defaults,t),r=n.transitional,o=n.paramsSerializer,i=n.headers;void 0!==r&&ke.assertOptions(r,{silentJSONParsing:Ue.transitional(Ue.boolean),forcedJSONParsing:Ue.transitional(Ue.boolean),clarifyTimeoutError:Ue.transitional(Ue.boolean)},!1),null!=o&&(q.isFunction(o)?t.paramsSerializer={serialize:o}:ke.assertOptions(o,{encode:Ue.function,serialize:Ue.function},!0)),t.method=(t.method||this.defaults.method||"get").toLowerCase();var a=i&&q.merge(i.common,i[t.method]);i&&q.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete i[e]})),t.headers=pe.concat(a,i);var s=[],u=!0;this.interceptors.request.forEach((function(e){"function"==typeof e.runWhen&&!1===e.runWhen(t)||(u=u&&e.synchronous,s.unshift(e.fulfilled,e.rejected))}));var c,f=[];this.interceptors.response.forEach((function(e){f.push(e.fulfilled,e.rejected)}));var l,d=0;if(!u){var p=[Te.bind(this),void 0];for(p.unshift.apply(p,s),p.push.apply(p,f),l=p.length,c=Promise.resolve(t);d0;)o._listeners[t](e);o._listeners=null}})),this.promise.then=function(e){var t,n=new Promise((function(e){o.subscribe(e),t=e})).then(e);return n.cancel=function(){o.unsubscribe(t)},n},n((function(e,t,n){o.reason||(o.reason=new ve(e,t,n),r(o.reason))}))}return r(e,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]}},{key:"unsubscribe",value:function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}}}],[{key:"source",value:function(){var t;return{token:new e((function(e){t=e})),cancel:t}}}]),e}();var Le={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Le).forEach((function(e){var t=o(e,2),n=t[0],r=t[1];Le[r]=n}));var De=Le;var Ie=function e(t){var n=new Fe(t),r=a(Fe.prototype.request,n);return q.extend(r,Fe.prototype,n,{allOwnKeys:!0}),q.extend(r,n,null,{allOwnKeys:!0}),r.create=function(n){return e(Ce(t,n))},r}(ae);return Ie.Axios=Fe,Ie.CanceledError=ve,Ie.CancelToken=Be,Ie.isCancel=me,Ie.VERSION=Ne,Ie.toFormData=G,Ie.AxiosError=M,Ie.Cancel=Ie.CanceledError,Ie.all=function(e){return Promise.all(e)},Ie.spread=function(e){return function(t){return e.apply(null,t)}},Ie.isAxiosError=function(e){return q.isObject(e)&&!0===e.isAxiosError},Ie.mergeConfig=Ce,Ie.AxiosHeaders=pe,Ie.formToJSON=function(e){return oe(q.isHTMLForm(e)?new FormData(e):e)},Ie.getAdapter=Re,Ie.HttpStatusCode=De,Ie.default=Ie,Ie})); +//# sourceMappingURL=axios.min.js.map diff --git a/apps/showcase/static/js/prism.js b/apps/showcase/static/js/prism.js index a78aae8af..4cfc5759f 100644 --- a/apps/showcase/static/js/prism.js +++ b/apps/showcase/static/js/prism.js @@ -1,8 +1,10 @@ -/* PrismJS 1.28.0 -https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+python */ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+python&plugins=remove-initial-line-feed+normalize-whitespace */ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; !function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism); Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; +"undefined"!=typeof Prism&&"undefined"!=typeof document&&Prism.hooks.add("before-sanity-check",(function(e){if(e.code){var n=e.element.parentNode,o=/(?:^|\s)keep-initial-line-feed(?:\s|$)/;!n||"pre"!==n.nodeName.toLowerCase()||o.test(n.className)||o.test(e.element.className)||(e.code=e.code.replace(/^(?:\r?\n|\r)/,""))}})); +!function(){if("undefined"!=typeof Prism){var e=Object.assign||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},t={"remove-trailing":"boolean","remove-indent":"boolean","left-trim":"boolean","right-trim":"boolean","break-lines":"number",indent:"number","remove-initial-line-feed":"boolean","tabs-to-spaces":"number","spaces-to-tabs":"number"};n.prototype={setDefaults:function(t){this.defaults=e(this.defaults,t)},normalize:function(t,n){for(var r in n=e(this.defaults,n)){var i=r.replace(/-(\w)/g,(function(e,t){return t.toUpperCase()}));"normalize"!==r&&"setDefaults"!==i&&n[r]&&this[i]&&(t=this[i].call(this,t,n[r]))}return t},leftTrim:function(e){return e.replace(/^\s+/,"")},rightTrim:function(e){return e.replace(/\s+$/,"")},tabsToSpaces:function(e,t){return t=0|t||4,e.replace(/\t/g,new Array(++t).join(" "))},spacesToTabs:function(e,t){return t=0|t||4,e.replace(RegExp(" {"+t+"}","g"),"\t")},removeTrailing:function(e){return e.replace(/\s*?$/gm,"")},removeInitialLineFeed:function(e){return e.replace(/^(?:\r?\n|\r)/,"")},removeIndent:function(e){var t=e.match(/^[^\S\n\r]*(?=\S)/gm);return t&&t[0].length?(t.sort((function(e,t){return e.length-t.length})),t[0].length?e.replace(RegExp("^"+t[0],"gm"),""):e):e},indent:function(e,t){return e.replace(/^[^\S\n\r]*(?=\S)/gm,new Array(++t).join("\t")+"$&")},breakLines:function(e,t){t=!0===t?80:0|t||80;for(var n=e.split("\n"),i=0;it&&(o[l]="\n"+o[l],a=s)}n[i]=o.join("")}return n.join("\n")}},"undefined"!=typeof module&&module.exports&&(module.exports=n),Prism.plugins.NormalizeWhitespace=new n({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-sanity-check",(function(e){var n=Prism.plugins.NormalizeWhitespace;if((!e.settings||!1!==e.settings["whitespace-normalization"])&&Prism.util.isActive(e.element,"whitespace-normalization",!0))if(e.element&&e.element.parentNode||!e.code){var r=e.element.parentNode;if(e.code&&r&&"pre"===r.nodeName.toLowerCase()){for(var i in null==e.settings&&(e.settings={}),t)if(Object.hasOwnProperty.call(t,i)){var o=t[i];if(r.hasAttribute("data-"+i))try{var a=JSON.parse(r.getAttribute("data-"+i)||"true");typeof a===o&&(e.settings[i]=a)}catch(e){}}for(var l=r.childNodes,s="",c="",u=!1,m=0;m."), + context + ); + } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context @@ -3538,7 +3544,7 @@ // render self var vnode; try { - // There's no need to maintain a stack becaues all render fns are called + // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm; @@ -5437,7 +5443,7 @@ value: FunctionalRenderContext }); - Vue.version = '2.6.10'; + Vue.version = '2.6.12'; /* */ @@ -6110,7 +6116,7 @@ } } - function removeVnodes (parentElm, vnodes, startIdx, endIdx) { + function removeVnodes (vnodes, startIdx, endIdx) { for (; startIdx <= endIdx; ++startIdx) { var ch = vnodes[startIdx]; if (isDef(ch)) { @@ -6221,7 +6227,7 @@ refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { - removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); + removeVnodes(oldCh, oldStartIdx, oldEndIdx); } } @@ -6313,7 +6319,7 @@ if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { - removeVnodes(elm, oldCh, 0, oldCh.length - 1); + removeVnodes(oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } @@ -6542,7 +6548,7 @@ // destroy old node if (isDef(parentElm)) { - removeVnodes(parentElm, [oldVnode], 0, 0); + removeVnodes([oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } @@ -7643,7 +7649,7 @@ // skip the update if old and new VDOM state is the same. // `value` is handled separately because the DOM value may be temporarily // out of sync with VDOM state due to focus, composition and modifiers. - // This #4521 by skipping the unnecesarry `checked` update. + // This #4521 by skipping the unnecessary `checked` update. cur !== oldProps[key] ) { // some property updates can throw @@ -9248,7 +9254,7 @@ var startTagClose = /^\s*(\/?)>/; var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>")); var doctype = /^]+>/i; - // #7298: escape - to avoid being pased as HTML comment when inlined in page + // #7298: escape - to avoid being passed as HTML comment when inlined in page var comment = /^ can only appear at the root level inside " + - "the receiving the component", + "the receiving component", el ); } @@ -10720,7 +10726,7 @@ /* */ - var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*(?:[\w$]+)?\s*\(/; + var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/; var fnInvokeRE = /\([^)]*?\);*$/; var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/; @@ -11489,6 +11495,8 @@ var range = node.rawAttrsMap[name]; if (name === 'v-for') { checkFor(node, ("v-for=\"" + value + "\""), warn, range); + } else if (name === 'v-slot' || name[0] === '#') { + checkFunctionParameterExpression(value, (name + "=\"" + value + "\""), warn, range); } else if (onRE.test(name)) { checkEvent(value, (name + "=\"" + value + "\""), warn, range); } else { @@ -11508,9 +11516,9 @@ } function checkEvent (exp, text, warn, range) { - var stipped = exp.replace(stripStringRE, ''); - var keywordMatch = stipped.match(unaryOperatorsRE); - if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { + var stripped = exp.replace(stripStringRE, ''); + var keywordMatch = stripped.match(unaryOperatorsRE); + if (keywordMatch && stripped.charAt(keywordMatch.index - 1) !== '$') { warn( "avoid using JavaScript unary operator as property name: " + "\"" + (keywordMatch[0]) + "\" in expression " + (text.trim()), @@ -11565,6 +11573,19 @@ } } + function checkFunctionParameterExpression (exp, text, warn, range) { + try { + new Function(exp, ''); + } catch (e) { + warn( + "invalid function parameter expression: " + (e.message) + " in\n\n" + + " " + exp + "\n\n" + + " Raw expression: " + (text.trim()) + "\n", + range + ); + } + } + /* */ var range = 2; diff --git a/apps/showcase/templates/index.html b/apps/showcase/templates/index.html index f77730ba2..5b7b89836 100644 --- a/apps/showcase/templates/index.html +++ b/apps/showcase/templates/index.html @@ -1,7 +1,21 @@ [[extend 'layout.html']] + +
              + + + + + + + @@ -38,7 +52,7 @@ - + @@ -49,7 +63,7 @@ - + diff --git a/apps/showcase/templates/show.html b/apps/showcase/templates/show.html index 166ab0898..7a55a6488 100644 --- a/apps/showcase/templates/show.html +++ b/apps/showcase/templates/show.html @@ -5,7 +5,7 @@ Back to Examples [[if executable:]] -Run Example +Run Example [[else:]] Run this application locally to try examples [[pass]] diff --git a/apps/showcase/vue_components_examples/components/vueform.py b/apps/showcase/vue_components_examples/components/vueform.py index 1e7479d4d..301cff9f9 100644 --- a/apps/showcase/vue_components_examples/components/vueform.py +++ b/apps/showcase/vue_components_examples/components/vueform.py @@ -61,7 +61,7 @@ def __init__( self.path_check = path + "/check" self.db = db self.signer = signer or URLSigner(session) - self.__prerequisites__ = [self.signer] + self.__prerequisites__ = [db, self.signer] # session to be added by the signer self.validate = validate # Creates entry points for giving the blank form, and processing form submissions. # There are three entry points: diff --git a/apps/tagged_posts/__init__.py b/apps/tagged_posts/__init__.py new file mode 100644 index 000000000..aecd78c30 --- /dev/null +++ b/apps/tagged_posts/__init__.py @@ -0,0 +1,15 @@ +# check compatibility +import py4web + +assert py4web.check_compatible("0.1.20190709.1") + +# by importing db you expose it to the _dashboard/dbadmin +from .models import db + +# by importing controllers you expose the actions defined in it +from . import controllers + +# optional parameters +__version__ = "0.0.0" +__author__ = "you " +__license__ = "anything you want" diff --git a/apps/tagged_posts/common.py b/apps/tagged_posts/common.py new file mode 100644 index 000000000..428f7499c --- /dev/null +++ b/apps/tagged_posts/common.py @@ -0,0 +1,214 @@ +""" +This file defines cache, session, and translator T object for the app +These are fixtures that every app needs so probably you will not be editing this file +""" +import os +import sys +from py4web import Session, Cache, Translator, Flash, DAL, Field, action +from py4web.server_adapters.logging_utils import make_logger +from py4web.utils.mailer import Mailer +from py4web.utils.auth import Auth +from py4web.utils.downloader import downloader +from pydal.tools.tags import Tags +from pydal.tools.scheduler import Scheduler +from py4web.utils.factories import ActionFactory +from . import settings + +# ####################################################### +# implement custom loggers form settings.LOGGERS +# ####################################################### +logger = make_logger("py4web:" + settings.APP_NAME, settings.LOGGERS) + +# ####################################################### +# connect to db +# ####################################################### +db = DAL( + settings.DB_URI, + folder=settings.DB_FOLDER, + pool_size=settings.DB_POOL_SIZE, + migrate=settings.DB_MIGRATE, + fake_migrate=settings.DB_FAKE_MIGRATE, +) + +# ####################################################### +# define global objects that may or may not be used by the actions +# ####################################################### +cache = Cache(size=1000) +T = Translator(settings.T_FOLDER) + +# ####################################################### +# pick the session type that suits you best +# ####################################################### +if settings.SESSION_TYPE == "cookies": + session = Session(secret=settings.SESSION_SECRET_KEY) + +elif settings.SESSION_TYPE == "redis": + import redis + + host, port = settings.REDIS_SERVER.split(":") + # for more options: https://github.com/andymccurdy/redis-py/blob/master/redis/client.py + conn = redis.Redis(host=host, port=int(port)) + conn.set = ( + lambda k, v, e, cs=conn.set, ct=conn.ttl: cs(k, v, ct(k)) + if ct(k) >= 0 + else cs(k, v, e) + ) + session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) + +elif settings.SESSION_TYPE == "memcache": + import memcache, time + + conn = memcache.Client(settings.MEMCACHE_CLIENTS, debug=0) + session = Session(secret=settings.SESSION_SECRET_KEY, storage=conn) + +elif settings.SESSION_TYPE == "database": + from py4web.utils.dbstore import DBStore + + session = Session(secret=settings.SESSION_SECRET_KEY, storage=DBStore(db)) + +# ####################################################### +# Instantiate the object and actions that handle auth +# ####################################################### +auth = Auth(session, db, define_tables=False) +auth.use_username = True +auth.param.registration_requires_confirmation = settings.VERIFY_EMAIL +auth.param.registration_requires_approval = settings.REQUIRES_APPROVAL +auth.param.login_after_registration = settings.LOGIN_AFTER_REGISTRATION +auth.param.allowed_actions = settings.ALLOWED_ACTIONS +auth.param.login_expiration_time = 3600 +auth.param.password_complexity = {"entropy": settings.PASSWORD_ENTROPY} +auth.param.block_previous_password_num = 3 +auth.param.default_login_enabled = settings.DEFAULT_LOGIN_ENABLED +auth.define_tables() +auth.fix_actions() + +flash = auth.flash + +# ####################################################### +# Configure email sender for auth +# ####################################################### +if settings.SMTP_SERVER: + auth.sender = Mailer( + server=settings.SMTP_SERVER, + sender=settings.SMTP_SENDER, + login=settings.SMTP_LOGIN, + tls=settings.SMTP_TLS, + ssl=settings.SMTP_SSL, + ) + +# ####################################################### +# Create a table to tag users as group members +# ####################################################### +if auth.db: + groups = Tags(db.auth_user, "groups") + +# ####################################################### +# Enable optional auth plugin +# ####################################################### +if settings.USE_PAM: + from py4web.utils.auth_plugins.pam_plugin import PamPlugin + + auth.register_plugin(PamPlugin()) + +if settings.USE_LDAP: + from py4web.utils.auth_plugins.ldap_plugin import LDAPPlugin + + auth.register_plugin(LDAPPlugin(db=db, groups=groups, **settings.LDAP_SETTINGS)) + +if settings.OAUTH2GOOGLE_CLIENT_ID: + from py4web.utils.auth_plugins.oauth2google import OAuth2Google # TESTED + + auth.register_plugin( + OAuth2Google( + client_id=settings.OAUTH2GOOGLE_CLIENT_ID, + client_secret=settings.OAUTH2GOOGLE_CLIENT_SECRET, + callback_url="auth/plugin/oauth2google/callback", + ) + ) + +if settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE: + from py4web.utils.auth_plugins.oauth2google_scoped import ( + OAuth2GoogleScoped, + ) # TESTED + + auth.register_plugin( + OAuth2GoogleScoped( + secrets_file=settings.OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE, + scopes=[], # Put here any scopes you want in addition to login + db=db, # Needed to store credentials in auth_credentials + ) + ) + +if settings.OAUTH2GITHUB_CLIENT_ID: + from py4web.utils.auth_plugins.oauth2github import OAuth2Github # TESTED + + auth.register_plugin( + OAuth2Github( + client_id=settings.OAUTH2GITHUB_CLIENT_ID, + client_secret=settings.OAUTH2GITHUB_CLIENT_SECRET, + callback_url="auth/plugin/oauth2github/callback", + ) + ) + +if settings.OAUTH2FACEBOOK_CLIENT_ID: + from py4web.utils.auth_plugins.oauth2facebook import OAuth2Facebook # UNTESTED + + auth.register_plugin( + OAuth2Facebook( + client_id=settings.OAUTH2FACEBOOK_CLIENT_ID, + client_secret=settings.OAUTH2FACEBOOK_CLIENT_SECRET, + callback_url="auth/plugin/oauth2facebook/callback", + ) + ) + +if settings.OAUTH2OKTA_CLIENT_ID: + from py4web.utils.auth_plugins.oauth2okta import OAuth2Okta # TESTED + + auth.register_plugin( + OAuth2Okta( + client_id=settings.OAUTH2OKTA_CLIENT_ID, + client_secret=settings.OAUTH2OKTA_CLIENT_SECRET, + callback_url="auth/plugin/oauth2okta/callback", + ) + ) + +# ####################################################### +# Define a convenience action to allow users to download +# files uploaded and reference by Field(type='upload') +# ####################################################### +if settings.UPLOAD_FOLDER: + + @action("download/") + @action.uses(db) + def download(filename): + return downloader(db, settings.UPLOAD_FOLDER, filename) + + # To take advantage of this in Form(s) + # for every field of type upload you MUST specify: + # + # field.upload_path = settings.UPLOAD_FOLDER + # field.download_url = lambda filename: URL('download/%s' % filename) + +# ####################################################### +# Define and optionally start the scheduler +# ####################################################### +if settings.USE_SCHEDULER: + scheduler = Scheduler( + db, logger=logger, max_concurrent_runs=settings.SCHEDULER_MAX_CONCURRENT_RUNS + ) + scheduler.start() +else: + scheduler = None + +# ####################################################### +# Enable authentication +# ####################################################### +auth.enable(uses=(session, T, db), env=dict(T=T)) + +# ####################################################### +# Define convenience decorators +# They can be used instead of @action and @action.uses +# They should NEVER BE MIXED with @action and @action.uses +# ####################################################### +unauthenticated = ActionFactory(db, session, T, flash, auth) +authenticated = ActionFactory(db, session, T, flash, auth.user) diff --git a/apps/tagged_posts/controllers.py b/apps/tagged_posts/controllers.py new file mode 100644 index 000000000..115ffc398 --- /dev/null +++ b/apps/tagged_posts/controllers.py @@ -0,0 +1,54 @@ +from py4web import action, request +from .common import auth +from .models import db, parse_post_content + +@action("index") +@action.uses("index.html", auth.user) +def index(): + return dict(message="hello world") + +@action("api/tags", method="GET") +@action.uses(auth.user) +def get_api_tags(): + """retrieve known tags""" + rows = db(db.tag_item).select( + db.tag_item.name, + orderby=db.tag_item.name, + groupby=db.tag_item.name) + return {"tags": [row.name for row in rows]} + +@action("api/posts", method="GET") +@action.uses(auth.user) +def get_api_posts(): + """retrieve posts and users metadata""" + if "tags" in request.query: + tags = request.query.get("tags").split(",") + query = (db.post_item.id==db.tag_item.post_item_id)&(db.tag_item.name.belongs(tags)) + else: + query = db.post_item + # get selected posts + rows = db(query).select( + db.post_item.ALL, + groupby=db.post_item.id, + orderby=~db.post_item.created_on, + limitby=(0,100)) + # get usernames for authors of those posts + users = { + user.id: user.username for user in + db(db.auth_user.id.belongs(row.created_by for row in rows)).select()} + return {"posts": rows.as_list(), "users": users} + +@action("api/posts", method="POST") +@action.uses(auth.user) +def post_api_posts(): + """submit a new post""" + content = request.json.get("content") + res = db.post_item.validate_and_insert(content=content) + if res["id"]: parse_post_content(content, res["id"]) + return res + +@action("api/posts/", method="DELETE") +@action.uses(auth.user) +def delete_api_posts(post_item_id): + """delete a a post""" + return {"deleted": db(db.post_item.id==post_item_id).delete()} diff --git a/apps/tagged_posts/models.py b/apps/tagged_posts/models.py new file mode 100644 index 000000000..2117a8f82 --- /dev/null +++ b/apps/tagged_posts/models.py @@ -0,0 +1,24 @@ +""" +This file defines the database models +""" + +from .common import db, Field, auth +from pydal.validators import * +import re + +db.define_table( + "post_item", + Field("content", "text"), + auth.signature) + +db.define_table( + "tag_item", + Field("name"), + Field("post_item_id", "reference post_item") +) + +def parse_post_content(content, post_item_id): + for word in re.compile(r"\#\w+").findall(content): + db.tag_item.insert(name=word[1:], post_item_id=post_item_id) + + diff --git a/apps/tagged_posts/settings.py b/apps/tagged_posts/settings.py new file mode 100644 index 000000000..c881050fe --- /dev/null +++ b/apps/tagged_posts/settings.py @@ -0,0 +1,122 @@ +""" +This is an optional file that defined app level settings such as: +- database settings +- session settings +- i18n settings +This file is provided as an example: +""" +import os +from py4web.core import required_folder + +# mode (default or development) +MODE = "development" + +# db settings +APP_FOLDER = os.path.dirname(__file__) +APP_NAME = os.path.split(APP_FOLDER)[-1] + +# DB_FOLDER: Sets the place where migration files will be created +# and is the store location for SQLite databases +DB_FOLDER = required_folder(APP_FOLDER, "databases") +DB_URI = "sqlite://storage.db" +DB_POOL_SIZE = 1 +DB_MIGRATE = True +DB_FAKE_MIGRATE = False + +# location where static files are stored: +STATIC_FOLDER = required_folder(APP_FOLDER, "static") + +# location where to store uploaded files: +UPLOAD_FOLDER = required_folder(APP_FOLDER, "uploads") + +# send verification email on registration +VERIFY_EMAIL = MODE != "development" + +# complexity of the password 0: no constraints, 50: safe! +PASSWORD_ENTROPY = 0 if MODE == "development" else 50 + +# account requires to be approved ? +REQUIRES_APPROVAL = False + +# auto login after registration +# requires False VERIFY_EMAIL & REQUIRES_APPROVAL +LOGIN_AFTER_REGISTRATION = False + +# ALLOWED_ACTIONS in API / default Forms: +# ["all"] +# ["login", "logout", "request_reset_password", "reset_password", \ +# "change_password", "change_email", "profile", "config", "register", +# "verify_email", "unsubscribe"] +# Note: if you add "login", add also "logout" +ALLOWED_ACTIONS = ["all"] + +# email settings +SMTP_SSL = False +SMTP_SERVER = None +SMTP_SENDER = "you@example.com" +SMTP_LOGIN = "username:password" +SMTP_TLS = False + +# session settings +SESSION_TYPE = "cookies" +SESSION_SECRET_KEY = None # or replace with your own secret +MEMCACHE_CLIENTS = ["127.0.0.1:11211"] +REDIS_SERVER = "localhost:6379" + +# logger settings +LOGGERS = [ + "warning:stdout" +] # syntax "severity:filename:format" filename can be stderr or stdout + +# Disable default login when using OAuth +DEFAULT_LOGIN_ENABLED = True + +# single sign on Google (will be used if provided) +OAUTH2GOOGLE_CLIENT_ID = None +OAUTH2GOOGLE_CLIENT_SECRET = None + +# Single sign on Google, with stored credentials for scopes (will be used if provided). +# set it to something like os.path.join(APP_FOLDER, "private/credentials.json" +OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE = None + +# single sign on Okta (will be used if provided. Please also add your tenant +# name to py4web/utils/auth_plugins/oauth2okta.py. You can replace the XXX +# instances with your tenant name.) +OAUTH2OKTA_CLIENT_ID = None +OAUTH2OKTA_CLIENT_SECRET = None + +# single sign on Google (will be used if provided) +OAUTH2FACEBOOK_CLIENT_ID = None +OAUTH2FACEBOOK_CLIENT_SECRET = None + +# single sign on GitHub (will be used if provided) +OAUTH2GITHUB_CLIENT_ID = None +OAUTH2GITHUB_CLIENT_SECRET = None + +# enable PAM +USE_PAM = False + +# enable LDAP +USE_LDAP = False +LDAP_SETTINGS = { + "mode": "ad", # Microsoft Active Directory + "server": "mydc.domain.com", # FQDN or IP of one Domain Controller + "base_dn": "cn=Users,dc=domain,dc=com", # base dn, i.e. where the users are located +} + +# i18n settings +T_FOLDER = required_folder(APP_FOLDER, "translations") + +# Scheduler settings +USE_SCHEDULER = False +SCHEDULER_MAX_CONCURRENT_RUNS = 1 + +# Celery settings (alternative to the build-in scheduler) +USE_CELERY = False +CELERY_BROKER = "redis://localhost:6379/0" + +# try import private settings +try: + from .settings_private import * +except (ImportError, ModuleNotFoundError): + pass diff --git a/apps/tagged_posts/static/README.md b/apps/tagged_posts/static/README.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/tagged_posts/static/README.md @@ -0,0 +1 @@ + diff --git a/apps/tagged_posts/static/css/no.css b/apps/tagged_posts/static/css/no.css new file mode 100644 index 000000000..13dabcf41 --- /dev/null +++ b/apps/tagged_posts/static/css/no.css @@ -0,0 +1,543 @@ +/***************************************************** + no.css version 2020-08-09.1 + + Designed to style pages without need for custom classes. + headers, paragraphs, buttons, tables, forms, + nav menus, alerts, and dialogs are styled automatically. + The only custom classes are color names, grid column sizes, + and a few convenience ones. + + Grid: + columns, col, c25, c33, c50, c66, c75 + Colors: + black, white, default, success, warning, error, info, transparent + Effects: + accordion, close, tags-list + Utils: + fill, padded + + License: MIT + *****************************************************/ + +/**************************************************** + global style + ****************************************************/ + +*, *:after, *:before { + border:0; + margin:0; + padding:0; + box-sizing: inherit; + color: inherit; +} + +html, body { + max-width: 100vw; + overflow-x: hidden; + box-sizing: border-box; +} + +body { + font-family: "Roboto", Helvetica, Arial, sans-serif; + line-height: 1.8em; + min-height: 100vh; + display: grid; + grid-template-rows: auto 1fr auto; +} + +/**************************************************** + elements style + ****************************************************/ + +p { + text-align:justify +} + +b, label, strong { + font-weight:bold +} + +ul { + list-style-type:none; + padding-left:20px +} + +a { + text-decoration:none; + color:#0074d9; + white-space:nowrap +} + +a:hover { + cursor:pointer +} + +h1,h2,h3,h4,h5,h6{ + font-weight:bold; + line-height: 1em; +} + +h1{ + font-size: 4em; + margin:1.0em 0 0.25em 0 +} + +h2{ + font-size: 2.4em; + margin:0.9em 0 0.25em 0 +} + +h3{ + font-size:1.8em; + margin:0.8em 0 0.25em 0 +} + +h4{ + font-size:1.6em; + margin:0.7em 0 0.30em 0 +} + +h5{ + font-size:1.4em; + margin:0.6em 0 0.40em 0 +} + +h6{ + font-size:1.2em; + margin:0.5em 0 0.50em 0 +} + +header, footer { + display:block; + width:100%; +} + +code { + background: #f4f5f6; + border-radius: .4rem; + font-size: 90; + margin: 0 .2rem; + padding: .2rem .5rem; + white-space: nowrap; +} + +p,li,button,fieldset,input,select,textarea,blockquote,table { + margin-bottom: 1.0rem; +} + +/**************************************************** + table + ****************************************************/ + +table { + border-collapse:collapse; + width: 100% +} + +tbody tr:hover { + background-color:#fbf6d9 +} + +thead tr { + background-color:#f1f1f1 +} + +tbody tr { + border-bottom:2px solid #f1f1f1 +} + +td, th { + padding: 4px 8px; + text-align: left; + vertical-align:top +} + +thead th { + vertical-align:bottom +} + +@media (min-width: 40rem) { + table { + display: table; + overflow-x: initial; + } +} + +/**************************************************** + buttons + ****************************************************/ + +[role="button"], button, input[type='button'], input[type='reset'], input[type='submit'] { + background-color: #0074d9; + border-radius: 5px; + margin-right: 10px; + margin-bottom: 10px; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 1.1rem; + font-weight: 300; + height: 1.8rem; + line-height: 1.8rem; + padding: 0 1.0rem; + text-align: center; + text-decoration: none; + white-space: nowrap; + min-width: 100px; +} + +[role="button"]:focus, [role="button"]:hover, button:focus, button:hover, input[type='button']:focus, input[type='button']:hover, input[type='reset']:focus, input[type='reset']:hover, input[type='submit']:focus, input[type='submit']:hover { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +/**************************************************** + forms + ****************************************************/ + +input[type='color'], input[type='date'], input[type='datetime'], input[type='time'], input[type='datetime-local'], input[type='email'], input[type='month'], input[type='number'], input[type='password'], input[type='search'], input[type='tel'], input[type='text'], input[type='url'], input[type='week'], input:not([type]), textarea, select { + -webkit-appearance: none; + background-color: transparent; + border: 0.1rem solid #d1d1d1; + border-radius: 5px; + box-shadow: none; + box-sizing: inherit; + font-family: monospace; + font-size: 1.2em; + padding: .5em 1.0em .5em; + width: 100%; +} + +input[type='color']:focus, input[type='date']:focus, input[type='time']:focus, input[type='datetime']:focus, input[type='datetime-local']:focus, input[type='email']:focus, input[type='month']:focus, input[type='number']:focus, input[type='password']:focus, input[type='search']:focus, input[type='tel']:focus, input[type='text']:focus, input[type='url']:focus, input[type='week']:focus, input:not([type]):focus, textarea:focus, select:focus { + border-color: #0074d9; + outline: 0; +} + +select { + background: url('data:image/svg+xml;utf8,') center right no-repeat; +} + +select[multiple] { + background: none; + height: auto; +} + +textarea { + min-height: 6.5rem; +} + +fieldset { + border-width: 0; + padding: 0; +} + +input[type='checkbox'], input[type='radio'] { + display: inline; +} + +[disabled] { + cursor: default; + opacity: .5; +} + +/**************************************************** + grid and page formatting + ****************************************************/ + +body > center > * { + text-align: initial; + max-width: 900px; + margin-left: auto; + margin-right: auto; +} + +.columns { + display: table; + width: 100%; +} + +.columns .columns { + margin: 0 -1.5em; +} + +@media (min-width:600px) { + .col,.c25,.c33,.c50,.c66,.c75 { + padding: 1.5em; + display: table-cell; + vertical-align: top; + } + .c25 { width: 24.9%; } + .c33 { width: 33.3%; } + .c50 { width: 49.9%; } + .c66 { width: 66.6%; } + .c75 { width: 74.9%; } +} + +@media (max-width:600px) { + .col,.c25,.c33,.c50,.c66,.c75 { + padding: 20px; + display: block; + vertical-align: top; + } +} + +.columns:after { + content: ""; + clear: both; + display: table; +} + +/**************************************************** + colors + ****************************************************/ + +.transparent{background-color:transparent;color:#111} +.default{background-color:#0074d9;color:white} +.success{background-color:#2ecc40;color:white} +.warning{background-color:#ffdc00;color:#111} +.error{background-color:#cc1f00;color:white} +.info{background-color:#f1f1f1;color:#111} +.white{background-color:white;color:#111;padding 5px;} +.black{background-color:#111;color:white} + +/**************************************************** + navigation and nested menu + ****************************************************/ + +nav { + position:relative; + padding: 0 1.5em; + display: table; + width: 100vw; + height: 40px; +} + +nav ul { + list-style:none; + position:relative; + padding:0 +} + +nav > input[type=checkbox], nav > label { + display: none; +} + +@media (min-width:600px) { + nav > * { + display: table-cell; + vertical-align: middle; + } + nav > ul:last-child { + float:right; + } + nav > ul > li { + padding: 1.5em 0.5em + } +} + +@media (max-width:600px) { + nav > ul { + display: table-column; + vertical-align: middle; + } + nav > label { + position: absolute; + display: inline-block; + top: 5px; + right: 20px; + font-size: 2em; + } + nav > a { + display: inline-block; + padding-top: 8px !important; + } + nav > ul { + display: block; + } + nav > input[type=checkbox]:not(:checked) ~ ul { + display: none; + } + nav > ul > li { + display: block; + text-align: center; + padding: 0.5em 0.2em; + } +} + +nav li:hover { + background-color: #0074d9 +} + +nav li:hover > a { + color: white +} + +nav a { + padding:0 5px; + text-decoration:none; + text-align:left; + color: black; +} + +nav.black ul ul a { + color: black +} + +nav.black a, nav.black > label { + color: white +} + +nav li { + position:relative; + margin:0; + padding:0; + display: inline-block +} + +nav ul ul { + border:1px solid #e1e1e1; + visibility:hidden; + opacity:0; + position:absolute; + top:90%; + left:-20px; + padding:0; + z-index:1000; + transition:all 0.2s ease-out; + list-style-type: none; + box-shadow:5px 5px 10px #666; + background-color: white +} + +nav ul ul li { + width: 100%; +} + +nav ul ul a { + padding:10px 15px; + color:#333; + font-weight:700; + font-size:12px; + line-height:16px; + display: block; + color: #111; +} + +nav ul ul ul { + top:0; + left:80%; + z-index:1100 +} + +nav li:hover > ul { + visibility:visible; + opacity:1 +} + +nav>li>ul>li:first-child:before{ + content:''; + position:absolute; + width:1px; + height:1px; + border:10px solid transparent; + left:50px; + top:-20px; + margin-left:-10px; + border-bottom-color:white +} + +/**************************************************** + modal + ****************************************************/ + +[role="dialog"] > div { + position:fixed; + z-index:9999; + top:0; + bottom:0; + left:0; + right:0; + background-color:rgba(0,0,0,0.8); + padding-top:20vh; + transition:opacity 500ms; + visibility:hidden; + opacity:0; +} +[role="dialog"] > input[type=checkbox] { display: none !important; } +input[type=checkbox]:checked ~ div {visibility:visible; opacity:1} +[role="dialog"] > div > *:not(.close) {width: 66%; margin-left:auto; margin-right:auto; border-radius: 5px;} +[role="dialog"] > div > .close, [role="alert"] > .close { + background: url('data:image/svg+xml;utf8,') center right no-repeat; + width:24px; height:24px; cursor: pointer; position:absolute; top:15px; right:15px; + cursor: pointer; +} + +/**************************************************** + accordion + ****************************************************/ + +.accordion>label{cursor:pointer} +.accordion>input ~ label:before {content:"\25b2"; color:#ddd} +.accordion>input:checked ~ label:before {content:"\25bc"; color:#ddd} +.accordion>input {display:none} +.accordion>input:checked ~ *:not(label) { + max-height: 1000px !important; + overflow:hidden !important; + -webkit-transition: max-height .3s ease-in; + transition: max-height .3s ease-in; +} + +.accordion>*:not(label) { + max-height: 0; + overflow: hidden; + margin: 0; + padding: 0; + -webkit-transition: max-height .3s ease-out; + transition: max-height .3s ease-out; +} + +/**************************************************** + convenience + ****************************************************/ + +[role="alert"] { + margin: 1.5em; + padding: 1.5em; + position: relative; + border-radius: 5px; + color: black; +} + +[role="alert"] > .close { + position: absolute; + top: 10px; + right: 10px; +} + +.padded { + padding: 1.5em; +} + +.fill { + width: 100%; +} + +ul.tags-list { + padding-left: 0; +} + +ul.tags-list li { + display: inline-block; + border-radius: 100px; + background-color: #111111; + color: white; + padding: 0.3em 0.8em 0.2em 0.8em; + line-height: 1.2em; + margin: 2px; + cursor: pointer; + opacity: 0.2; + text-transform: capitalize; +} + +ul.tags-list li[data-selected=true] { + opacity: 1.0; +} diff --git a/apps/tagged_posts/static/favicon.ico b/apps/tagged_posts/static/favicon.ico new file mode 100644 index 000000000..d0fbde681 Binary files /dev/null and b/apps/tagged_posts/static/favicon.ico differ diff --git a/apps/tagged_posts/static/js/index.js b/apps/tagged_posts/static/js/index.js new file mode 100644 index 000000000..4c2277761 --- /dev/null +++ b/apps/tagged_posts/static/js/index.js @@ -0,0 +1,55 @@ +"use strict"; + +let app = {}; +app.config = {}; +app.config.data = function() { + return { + content: "", + posts: [], + users: {}, + tags: [], + selected_tags: {} + }; +}; +app.config.methods = {}; +app.config.methods.submit = function() { + if (!app.vue.content.trim()) return; + axios.post("/tagged_posts/api/posts", {"content": app.vue.content}).then(function(res){ + app.vue.content = ""; + app.reload(); + }); +}; +app.config.methods.remove = function(item) { + axios.delete("/tagged_posts/api/posts/" + item.id).then(function(){ + app.reload(); + }); +}; +app.config.methods.toggle = function(tag) { + if (tag in this.selected_tags) { + delete this.selected_tags[tag]; + } else { + this.selected_tags[tag] = true; + } + app.reload(); +}; +app.config.methods.prettydate = function(date) { + console.log(date); + return date; +}; +app.reload = function() { + let tags = Object.keys(app.vue.selected_tags).join(","); + let posts_url = "/tagged_posts/api/posts"; + if (tags) posts_url += "?tags=" + tags; + axios.get(posts_url).then(function(res){ + // load new posts + app.vue.posts = res.data.posts; + // load new users + app.vue.users = res.data.users; + }); + axios.get("/tagged_posts/api/tags").then(function(res){ + app.vue.tags = res.data.tags; + }); +} + +app.vue = Vue.createApp(app.config).mount("#app"); +app.reload(); diff --git a/apps/tagged_posts/static/js/utils.js b/apps/tagged_posts/static/js/utils.js new file mode 100644 index 000000000..aa2bd424c --- /dev/null +++ b/apps/tagged_posts/static/js/utils.js @@ -0,0 +1,313 @@ +"user strict"; + +// Allows "bla {a} bla {b}".format({'a': 'hello', 'b': 'world'}) +if (!String.prototype.format) { + String.prototype.format = function (args) { + return this.replace(/\{([^}]+)\}/g, function (match, k) { return args[k]; }); + }; +} + +// Similar to jQuery $ but lighter +window.Q = function(sel, el) { return (el||document).querySelectorAll(sel); }; + +// Clone any object +Q.clone = function (data) { return JSON.parse(JSON.stringify(data)); }; + +Q.eval = function(text) { return eval('('+text+')'); }; + +// Given a url retuns an object with parsed query string +Q.get_query = function (source) { + source = source || window.location.search.substring(1); + var vars = {}, items = source.split('&'); + items.map(function (item) { + var pair = item.split('='); + vars[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + }); + return vars; +}; + +// a wrapper for fetch return a promise +Q.ajax = function(method, url, data, headers) { + var options = { + method: method, + referrerPolicy: 'no-referrer', + } + if (data){ + if ( !(data instanceof FormData)){ + options.headers = {'Content-type': 'application/json'}; + data = JSON.stringify(data); + } + options.body = data; + } + if (headers) for(var name in headers) options.headers[name] = headers[name]; + return new Promise(function(resolve, reject) { + fetch(url, options).then(function(res){ + res.text().then(function(body){ + res.data = body; + res.json = function(){return JSON.parse(body);}; + resolve(res); + }, reject);}).catch(reject); + }); +} + +Q.get = (url, headers) => Q.ajax("GET", url, null, headers); +Q.post = (url, data, headers) => Q.ajax("POST", url, data, headers); +Q.put = (url, data, headers) => Q.ajax("PUT", url, data, headers); +Q.delete = (url, headers) => Q.ajax("DELETE", url, null, headers); + +// Gets a cookie value +Q.get_cookie = function (name) { + var cookie = RegExp("" + name + "[^;]+").exec(document.cookie); + if (!cookie) return null; + return decodeURIComponent(!!cookie ? cookie.toString().replace(/^[^=]+./, "") : ""); +}; + +// Load components lazily: https://vuejs.org/v2/guide/components.html#Async-Components +Q.register_vue_component = function (name, src, onload) { + Vue.component(name, function (resolve, reject) { + Q.ajax('GET', src).then(function(res){resolve(onload(res));}); + }); +}; + +// Passes binary data to callback on drop of file in elem_id +Q.upload_helper = function (elem_id, callback) { + // function from http://jsfiddle.net/eliseosoto/JHQnk/ + var elem = document.getElementById(elem_id); + if (elem) { + var files = elem.files; + var reader = new FileReader(); + if (files && files[0]) { + reader.onload = function (event) { + var b64 = btoa(event.target.result); + callback(files[0].name, b64); + }; + reader.readAsBinaryString(files[0]); + } else { + callback(); + } + } +}; + +// Internationalization helper +// Usage: +// T.translations = {'dog': {0: 'no cane', 1: 'un case', 2: '{n} cani', 10: 'tanti cani'}}; +// T('dog').format({n: 5}) -> "5 cani" +var T = function (text) { + var obj = { + toString: function () { return T.format(text); }, + format: function (args) { return T.format(text, args); } + }; + return obj; +}; + +// Adds a convenience format method to the client-side translator object +T.format = function (text, args) { + args = args || {}; + translations = (T.translations || {})[text]; + var n = ('n' in args) ? args.n : 1; + if (translations) { + var k = 0; + for (var key in translations) { + var i = parseInt(key); + if (i <= n) k = i; else break; + } + text = translations[k]; + } + return text; +}; + +// Originally inspired by David Walsh (https://davidwalsh.name/javascript-debounce-function) +Q.debounce = (func, wait) => { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +}; + +// https://levelup.gitconnected.com/throttle-in-javascript-improve-your-applications-performance-984a4e020a3f +Q.throttle = (callback, delay) => { + let throttleTimeout = null; + let storedEvent = null; + const throttledEventHandler = event => { + storedEvent = event; + const shouldHandleEvent = !throttleTimeout; + if (shouldHandleEvent) { + callback(storedEvent); + storedEvent = null; + throttleTimeout = setTimeout(() => { + throttleTimeout = null; + if (storedEvent) { + throttledEventHandler(storedEvent); + } + }, delay); + } + }; + return throttledEventHandler; +}; + +// Renders a JSON field with tags_input +Q.tags_input = function(elem, options) { + if (typeof elem === typeof '') elem = Q(elem)[0]; + if (!options) + options = Q.eval(elem.dataset.options||'{}'); + // preferred set of tags + if (options.tags === undefined) options.tags = []; + // set to false to only allow selecting one of the specified tags + if (options.freetext === undefined) options.freetext = true; + // how to transform typed tags to convert to actual tags + if (options.transform === undefined) options.transform = function(x) {return x.toLowerCase();} + // how to display tags + if (options.labels === undefined) options.labels = {}; + // placeholder for the freetext field + if (options.placeholder === undefined) options.placeholder = ""; + // autocomplete list attribute https://www.w3schools.com/tags/tag_datalist.asp + if (options.autocomplete_list === undefined) options.autocomplete_list = null; + var tags = options.tags; + if(!elem) { console.log('Q.tags_input: elem '+selector+' not found'); return; } + elem.type = "hidden"; + var repl = document.createElement('ul'); + repl.classList.add('tags-list') + elem.parentNode.insertBefore(repl, elem); + var keys = Q.eval(elem.value||'[]'); + keys.map(function(x) { if(tags.indexOf(x)<0) tags.push(x); }); + var fill = function(elem, repl) { + repl.innerHTML = ''; + tags.forEach(function(x){ + var item = document.createElement('li'); + item.innerHTML = options.labels[x] || x; + item.dataset.value = x; + item.dataset.selected = keys.indexOf(x)>=0; + repl.appendChild(item); + item.onclick = function(evt){ + if(item.dataset.selected=='false') keys.push(x); else keys = keys.filter(function(y){ return x!=y; }); + item.dataset.selected = keys.indexOf(x)>=0; + elem.value = JSON.stringify(keys); + elem.dispatchEvent(new Event('input', { bubbles: true })); + }; + }); + }; + if (options.freetext) { + var inp = document.createElement('input'); + elem.parentNode.insertBefore(inp, elem); + inp.type = "text"; + inp.classList = elem.classList; + inp.placeholder = options.placeholder; + inp.setAttribute('list', options.autocomplete_list); + inp.onchange = function(evt) { + inp.value.split(',').map(function(x){ + x = options.transform(x.trim()); + if (options.regex && !x.match(options.regex)) return; + if (x && tags.indexOf(x)<0) tags.push(x); + if (x && keys.indexOf(x)<0) keys.push(x); + }); + inp.value = ''; + elem.value = JSON.stringify(keys); + elem.dispatchEvent(new Event('input', { bubbles: true })); + fill(elem, repl); + }; + } + fill(elem, repl); +}; + +// Password strenght calculator +Q.score_password = function(text) { + var score = -10, counters = {}; + text.split('').map(function(c){counters[c]=(counters[c]||0)+1; score += 5/counters[c];}); + [/\d/, /[a-z]/, /[A-Z]/, /\W/].map(function(re){ score += re.test(text)?10:0; }); + return Math.round(Math.max(0, score)); +}; + +// Apply the strength calculator to some input field +Q.score_input = function(elem, reference) { + if (typeof elem === typeof '') elem = Q(elem)[0]; + reference = reference || 100; + elem.style.backgroundPosition = 'center right'; + elem.style.backgroundRepeat = 'no-repeat'; + elem.onkeyup = elem.onchange = function(evt) { + var score = Q.score_password(elem.value.trim()); + var r = Math.round(255*Math.max(0,Math.min(2-2*score/reference,1))); + var g = Math.round(255*Math.max(0,Math.min(2*score/reference,1))); + elem.style.backgroundImage = (score==0)?"":("url('"+'data:image/svg+xml;utf8,'+"')"); + }; +}; + +// Traps a form submission +Q.trap_form = function (action, elem_id) { + Q('#' + elem_id + ' form:not(.no-form-trap)').forEach(function (form) { + var target = form.dataset['component_target'] || elem_id; + form.dataset['component_target'] = target; + var url = form.action; + if (url === '' || url === '#' || url === void 0) url = action; + var clickable = 'input[type=submit], input[type=image], button[type=submit], button:not([type])'; + form.querySelectorAll(clickable).forEach(function (elem) { + elem.onclick = function(event) { + event.preventDefault(); + form.querySelectorAll(clickable).forEach(function(elem) { + elem.disabled = true; + }); + var form_data = new FormData(form); // Allows file uploads. + Q.load_and_trap('POST', url, form_data, target); }; + }); + }); +}; + +// loads a component via ajax and traps its forms +Q.load_and_trap = function (method, url, form_data, target) { + method = (method || 'GET').toLowerCase(); + /* if target is not there, fill it with something that there isn't in the page*/ + if (target === void 0 || target === '') target = 'none'; + var onsuccess = function(res) { + if (res.redirected) window.location = res.url; + Q('#'+target)[0].innerHTML = res.data; + Q.trap_form(url, target); + var flash = res.headers.get('component-flash'); + if (flash) Q.flash(JSON.parse(flash)); + }; + var onerror = function(res) { + alert('ajax error'); + }; + Q.ajax(method, url, form_data).then(onsuccess).catch(onerror); +}; + +// Loads all ajax components +Q.handle_components = function() { + Q('ajax-component').forEach(function(elem) { + Q.load_and_trap('GET', elem.attributes.url.value, null, elem.attributes.id.value); + }); +}; + +// Displays flash messages +Q.handle_flash = function() { + var elem = Q('flash-alerts')[0]; + var make_delete_handler = function(node) { + return function(event) { + node.parentNode.removeChild(node); + }; + }; + var make_handler = function(elem) { + return function (event) { + var node = document.createElement("div"); + node.innerHTML = '
              {0}
              '.format([event.detail.message]); + node = Q('[role="alert"]', node)[0]; + node.classList.add(event.detail.class||'info'); + elem.appendChild(node); + Q('[role="alert"] .close',node)[0].onclick = make_delete_handler(node); + }; + }; + if (elem) { + elem.addEventListener('flash', make_handler(elem), false); + Q.flash = function(detail) {elem.dispatchEvent(new CustomEvent('flash', {detail: detail}));}; + if (elem.dataset.alert) Q.flash(Q.eval(elem.dataset.alert)); + } +}; + +Q.handle_components(); +Q.handle_flash(); +Q('input[type=text].type-list-string').forEach(function(elem){Q.tags_input(elem);}); +Q('input[type=text].type-list-integer').forEach(function(elem){Q.tags_input(elem, {regex:/[-+]?[\d]+/});}); +Q('input[name=password],input[name=new_password]').forEach(Q.score_input); diff --git a/apps/tagged_posts/tasks.py b/apps/tagged_posts/tasks.py new file mode 100644 index 000000000..441839bdc --- /dev/null +++ b/apps/tagged_posts/tasks.py @@ -0,0 +1,34 @@ +""" +To use celery tasks: +1) pip install -U "celery[redis]" +2) In settings.py: + USE_CELERY = True + CELERY_BROKER = "redis://localhost:6379/0" +3) Start "redis-server" +4) Start "celery -A apps.{appname}.tasks beat" +5) Start "celery -A apps.{appname}.tasks worker --loglevel=info" for each worker + +""" +from .common import settings, scheduler, db, Field + +# example of task that needs db access +@scheduler.task +def my_task(): + try: + # this task will be executed in its own thread, connect to db + db._adapter.reconnect() + # do something here + db.commit() + except: + # rollback on failure + db.rollback() + + +# run my_task every 10 seconds +scheduler.conf.beat_schedule = { + "my_first_task": { + "task": "apps.%s.tasks.my_task" % settings.APP_NAME, + "schedule": 10.0, + "args": (), + }, +} diff --git a/apps/tagged_posts/templates/README.md b/apps/tagged_posts/templates/README.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/tagged_posts/templates/README.md @@ -0,0 +1 @@ + diff --git a/apps/tagged_posts/templates/auth.html b/apps/tagged_posts/templates/auth.html new file mode 100644 index 000000000..8f4c6b73a --- /dev/null +++ b/apps/tagged_posts/templates/auth.html @@ -0,0 +1,15 @@ +[[extend "layout.html"]] + +
              + [[=form]] +
              diff --git a/apps/tagged_posts/templates/generic.html b/apps/tagged_posts/templates/generic.html new file mode 100644 index 000000000..c5f70a14b --- /dev/null +++ b/apps/tagged_posts/templates/generic.html @@ -0,0 +1,13 @@ +[[extend 'layout.html']] + + + +
              +
              + [[=BEAUTIFY(__vars__)]] +
              +
              diff --git a/apps/tagged_posts/templates/index.html b/apps/tagged_posts/templates/index.html new file mode 100644 index 000000000..d7a07f5c2 --- /dev/null +++ b/apps/tagged_posts/templates/index.html @@ -0,0 +1,39 @@ +[[extend 'layout.html']] + + + +
              +
              +
              + + +

              Filtered Posts

              +
              +
              +

              {{item.content}}

              + posted on {{prettydate(item.created_on)}} by {{users[item.created_by]}} + 🗑 +
              +
              +
              +
              +

              Filter by Tags

              +
              + {{tag}} +
              +
              +
              +
              + +[[block page_scripts]] + + + +[[end]] diff --git a/apps/tagged_posts/templates/layout.html b/apps/tagged_posts/templates/layout.html new file mode 100644 index 000000000..fea0a1417 --- /dev/null +++ b/apps/tagged_posts/templates/layout.html @@ -0,0 +1,71 @@ + + + + + + + + + + [[block page_head]][[end]] + + +
              + + +
              + +
              +
              + + +
              +
              + + [[include]] +
              +
              + +
              +

              + Made with py4web +

              +
              + + + + [[block page_scripts]][[end]] + diff --git a/apps/tagged_posts/translations/it.json b/apps/tagged_posts/translations/it.json new file mode 100644 index 000000000..33de46d28 --- /dev/null +++ b/apps/tagged_posts/translations/it.json @@ -0,0 +1 @@ +{"Hello World from {name}": {"0": "Salve Mondo da {name}"}, "thing": {"0": "cosa", "1": "cose"}} diff --git a/apps/todo/__init__.py b/apps/todo/__init__.py index 3017b3faf..4766d536d 100644 --- a/apps/todo/__init__.py +++ b/apps/todo/__init__.py @@ -2,7 +2,7 @@ from py4web import action, request, DAL, Field, Session, Cache, Condition # define session and cache objects -session = Session(secret="some secret") +session = Session() cache = Cache(size=1000) # define database and tables @@ -27,7 +27,7 @@ def index(): # example of GET/POST/DELETE RESTful APIs -@action("api") # a GET API function +@action("api", method="GET") # a GET API function @action.uses(session, db) # we load the session and db @action.uses(user_in_session) # then check we have a valid user in session def todo(): diff --git a/apps/todo/static/js/axios.min.js b/apps/todo/static/js/axios.min.js deleted file mode 100644 index 2d030546a..000000000 --- a/apps/todo/static/js/axios.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* axios v0.20.0 | (c) 2020 by Matt Zabriskie */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(4),a=n(22),u=n(10),c=r(u);c.Axios=s,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function s(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){u.headers[e]={}}),i.forEach(["post","put","patch"],function(e){u.headers[e]=i.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),i=n(16),s=n(5),a=n(17),u=n(20),c=n(21),f=n(14);e.exports=function(e){return new Promise(function(t,n){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"],(r.isBlob(p)||r.isFile(p))&&p.type&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=unescape(encodeURIComponent(e.auth.password))||"";d.Authorization="Basic "+btoa(h+":"+m)}var y=a(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),s(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in l?u(l.getAllResponseHeaders()):null,i=e.responseType&&"text"!==e.responseType?l.response:l.responseText,s={data:i,status:l.status,statusText:l.statusText,headers:r,config:e,request:l};o(t,n,s),l=null}},l.onabort=function(){l&&(n(f("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){n(f("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),n(f(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=(e.withCredentials||c(y))&&e.xsrfCookieName?i.read(e.xsrfCookieName):void 0;g&&(d[e.xsrfHeaderName]=g)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),n(e),l=null)}),p||(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),s===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(18),o=n(19);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){function n(e,t){return r.isPlainObject(e)&&r.isPlainObject(t)?r.merge(e,t):r.isPlainObject(t)?r.merge({},t):r.isArray(t)?t.slice():t}function o(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(e[o],t[o])}t=t||{};var i={},s=["url","method","data"],a=["headers","auth","proxy","params"],u=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],c=["validateStatus"];r.forEach(s,function(e){r.isUndefined(t[e])||(i[e]=n(void 0,t[e]))}),r.forEach(a,o),r.forEach(u,function(o){r.isUndefined(t[o])?r.isUndefined(e[o])||(i[o]=n(void 0,e[o])):i[o]=n(void 0,t[o])}),r.forEach(c,function(r){r in t?i[r]=n(e[r],t[r]):r in e&&(i[r]=n(void 0,e[r]))});var f=s.concat(a).concat(u).concat(c),p=Object.keys(e).concat(Object.keys(t)).filter(function(e){return f.indexOf(e)===-1});return r.forEach(p,o),i}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); -//# sourceMappingURL=axios.min.map \ No newline at end of file diff --git a/apps/todo/static/js/todo.js b/apps/todo/static/js/todo.js index 557b5b40a..fbaac3478 100644 --- a/apps/todo/static/js/todo.js +++ b/apps/todo/static/js/todo.js @@ -7,7 +7,7 @@ app.data.input = ''; // methods exposed to the view app.methods.edit = function(item_id) { }; app.methods.remove = function(item_id) { - axios.delete(app.api + '/' + item_id).then(function(){ + Q.delete(app.api + '/' + item_id).then(function(){ app.vue.items=app.vue.items.filter(function(item){ return item.id!=item_id; }); @@ -16,14 +16,14 @@ app.methods.remove = function(item_id) { app.methods.save = function(item_id) { var data = {}; data.info = app.vue.input; - axios.post(app.api, data).then(function(res){ - if (app.vue.input) app.vue.items.unshift({id:res.data.id, info: app.vue.input}); + Q.post(app.api, data).then(function(res){ + if (app.vue.input) app.vue.items.unshift({id:res.json().id, info: app.vue.input}); app.vue.input=''; }); }; // start the app app.vue = new Vue({el:"#vue", data: app.data, methods: app.methods}); -axios.get(app.api).then(function(res){ - app.vue.items = res.data.items; +Q.get(app.api).then(function(res){ + app.vue.items = res.json().items; }); diff --git a/apps/todo/static/js/utils.js b/apps/todo/static/js/utils.js index c0ba784d2..aa2bd424c 100644 --- a/apps/todo/static/js/utils.js +++ b/apps/todo/static/js/utils.js @@ -43,12 +43,18 @@ Q.ajax = function(method, url, data, headers) { return new Promise(function(resolve, reject) { fetch(url, options).then(function(res){ res.text().then(function(body){ - res.data = body; + res.data = body; res.json = function(){return JSON.parse(body);}; resolve(res); }, reject);}).catch(reject); }); } + +Q.get = (url, headers) => Q.ajax("GET", url, null, headers); +Q.post = (url, data, headers) => Q.ajax("POST", url, data, headers); +Q.put = (url, data, headers) => Q.ajax("PUT", url, data, headers); +Q.delete = (url, headers) => Q.ajax("DELETE", url, null, headers); + // Gets a cookie value Q.get_cookie = function (name) { var cookie = RegExp("" + name + "[^;]+").exec(document.cookie); diff --git a/apps/todo/templates/layout.html b/apps/todo/templates/layout.html index 31f7e177a..97553850c 100644 --- a/apps/todo/templates/layout.html +++ b/apps/todo/templates/layout.html @@ -17,8 +17,7 @@

              Example TODO App

              - - + diff --git a/default.nix b/default.nix new file mode 100644 index 000000000..da280d490 --- /dev/null +++ b/default.nix @@ -0,0 +1,57 @@ +let + nixpkgs-src = builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/tarball/nixos-23.05"; + }; + + pkgs = import nixpkgs-src {}; + + # This is the Python version that will be used. + myPython = pkgs.python311; + + pythonWithPkgs = myPython.withPackages (pythonPkgs: with pythonPkgs; [ + pip + setuptools + wheel + twine + black + isort + pytest + ]); + + lib-path = with pkgs; lib.makeLibraryPath [ + libffi + openssl + ]; + + shell = pkgs.mkShell { + buildInputs = [ + pythonWithPkgs + pkgs.zip + pkgs.memcached + pkgs.redis + pkgs.readline + pkgs.libffi + pkgs.openssl + pkgs.cmake + ]; + + shellHook = '' + # Allow the use of wheels. + SOURCE_DATE_EPOCH=$(date +%s) + VENV_PATH=/home/$USER/.venvs$(pwd)/venv${myPython.version} + # Augment the dynamic linker path + export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${lib-path}" + + # Setup the virtual environment if it doesn't already exist. + if test ! -d $VENV_PATH; then + python -m venv $VENV_PATH + fi + $VENV_PATH/bin/pip install -U -r requirements.txt + source $VENV_PATH/bin/activate + export LOCALE_ARCHIVE=/usr/lib/locale/locale-archive + export PYTHONPATH=$VENV_PATH/${myPython.sitePackages}/:$PYTHONPATH + ''; + }; +in + +shell \ No newline at end of file diff --git a/deployment_tools/docker/README.md b/deployment_tools/docker/README.md index e6a27ff00..ce117bcdf 100644 --- a/deployment_tools/docker/README.md +++ b/deployment_tools/docker/README.md @@ -14,7 +14,9 @@ you'll need to specify the version of all the components. You need a working Docker system, see the official site for installation info at https://docs.docker.com/get-docker/ -Download all the files in this directory to an empty one your system, then run: +Download the four files on top of this page to an empty directory on your system. If you download them with chrome based browser there is a big chance you need to remove the .txt part from the file names due to this bug: https://github.com/microsoft/vscode/issues/118436 + +Then run: # build an image called py4web from latest Ubuntu $ docker build . -t py4web @@ -62,3 +64,119 @@ You'll still need to set the dashboard password: - Using a modern IDE, you can also directly edit the files inside the containers - and even debug them! +## Combining py4web with Celery and Redis + +Celery can be used for handling long running background tasks. +Celery uses Redis as broker. Redis will be run in a seperate container. + +At the end of the docker-compose file add: + + redis: + restart: always + image: redis + ports: + - "6379:6379" + +In the app (based on _scaffold) settings.py: + + #Celery settings + USE_CELERY = True + CELERY_BROKER = "redis://redis:6379/0" + CELERY_BACKEND = "redis://redis:6379/0" + +In common.py: + + # ####################################################### + # Optionally configure celery + # ####################################################### + if settings.USE_CELERY: + from celery import Celery + + # to use "from .common import scheduler" and then use it according + # to celery docs, examples in tasks.py + scheduler = Celery( + "apps.%s.tasks" % settings.APP_NAME, broker=settings.CELERY_BROKER, backend=settings.CELERY_BACKEND) + scheduler.conf.broker_connection_retry_on_startup = True + +In the docker file use entrypoint.sh to get the scheduler going and start py4web. + + entrypoint.sh: + + #!/bin/bash + . /home/py4web/.venv/bin/activate + exec .venv/bin/celery -A apps.myapp.tasks beat & + exec .venv/bin/celery -A apps.myapp.tasks worker --loglevel=info & + exec py4web run --password_file password.txt --host 0.0.0.0 --port 8000 apps + +complete Dockerfile: + + FROM ubuntu:latest + + ARG user=py4web + ENV PY4WEB_ROOT=/home/$user + + RUN apt update && \ + apt install -y git locales locales-all python3.12 python3-pip python3.12-venv memcached && \ + service memcached restart && \ + groupadd -r $user && \ + useradd -m -r -g $user $user && \ + python3 -m venv $PY4WEB_ROOT/.venv && \ + . $PY4WEB_ROOT/.venv/bin/activate && \ + python3 -m pip install -U py4web psycopg2-binary && \ + python3 -m pip install -U "celery[redis]" + + ENV LC_ALL en_US.UTF-8 + ENV LANG en_US.UTF-8 + ENV LANGUAGE en_US.UTF-8 + + USER $user + + RUN . $PY4WEB_ROOT/.venv/bin/activate && \ + cd $PY4WEB_ROOT/ && py4web setup --yes apps + # use ./venv/bin/py4web set_password + COPY password.txt $PY4WEB_ROOT/. + + EXPOSE 8000 + + WORKDIR $PY4WEB_ROOT/ + COPY entrypoint.sh /usr/local/bin/ + ENTRYPOINT [ "entrypoint.sh" ] + +docker-compose.yml + + services: + + web: + build: . + ports: + - "8000:8000" + environment: + - PYDAL_URI=postgres://foo:bar@postgres:5432/baz + - PYDAL_URI2=mysql://root:secret@localhost/ursadina_gtd + volumes: + - ./apps:/home/py4web/apps + stdin_open: true + tty: true + depends_on: + - postgres + - redis + + postgres: + restart: always + image: postgres + environment: + - POSTGRES_USER=foo + - POSTGRES_PASSWORD=bar + - POSTGRES_DB=baz + - POSTGRES_PORT=5432 + ports: + - "5432:5432" + volumes: + - ./data/postgres:/var/lib/postgresql/data + redis: + restart: always + image: redis + ports: + - "6379:6379" + + diff --git a/deployment_tools/gae/Makefile b/deployment_tools/gae/Makefile index e3721f064..4320f19c1 100644 --- a/deployment_tools/gae/Makefile +++ b/deployment_tools/gae/Makefile @@ -4,18 +4,9 @@ install-gcloud-linux: upgrade-gcloud: gcloud components update setup: - mkdir -p lib - rm -rf lib/* - # - cat ../../requirements.txt \ - | grep -v gevent \ - | grep -v tornado \ - | grep -v gunicorn \ - | grep -v memcache > requirements.txt - echo mysqlclient >> requirements.txt - mkdir -p lib - # python3 -m pip install -U --no-deps py4web -t lib/ - cp -r ../../py4web lib/ + mkdir -p apps + echo "" > apps/__init__.py + echo "copy the apps you want to deploy under the new app" deploy: gcloud config set account ${email} gcloud config set project ${project} diff --git a/deployment_tools/gae/README.md b/deployment_tools/gae/README.md index ab116ce2f..e7cc07460 100644 --- a/deployment_tools/gae/README.md +++ b/deployment_tools/gae/README.md @@ -1,24 +1,28 @@ # To deploy code on Google App Engine: +## Setup your deployment folder + ``` -cd deployment_tools/gae +mkdir my-py4web-gae +cp -r /path/to/py4web/deployment_tools/gae/* my-py4web-gae +cd my-py4web-gae +make install-gcloud-linux +make upgrade-gcloud make setup -mkdir apps -touch apps/__init__.py -# symlink the apps that you want to deploy to GAE, for example: -cd apps -ln -s ../../../apps/_default . -ln -s ../../../apps/.service . -cd .. +# copy the apps that you want to deploy to GAE, for example: +cp -r /path/to/py4web/apps/_default apps/_default +(cp -r /path/to/py4web/apps/myapp apps/myapp) +# you may need to to symlink or copy the service folder (optional) +cp -r /path/to/py4web/apps/.service apps/ ``` -Then, you can either do: +## Deploy from the deployment folder ``` make deploy email={your email} project={your project} version={vesion} ``` -or if you have a gcloud configuration already configured, +If you have a gcloud configuration already set you can just do ``` gcloud app deploy diff --git a/deployment_tools/gae/app.yaml b/deployment_tools/gae/app.yaml index e9677ce82..f7ca4c66e 100644 --- a/deployment_tools/gae/app.yaml +++ b/deployment_tools/gae/app.yaml @@ -1,4 +1,4 @@ -runtime: python37 +runtime: python311 # Handlers define how to route requests to your application. handlers: diff --git a/deployment_tools/gae/main.py b/deployment_tools/gae/main.py index a9b53dfd3..3de0f7bc8 100644 --- a/deployment_tools/gae/main.py +++ b/deployment_tools/gae/main.py @@ -1,11 +1,12 @@ import os import site +import uuid site.addsitedir(os.path.join(os.path.dirname(__file__), 'lib')) from py4web.core import Reloader, bottle, Session -os.environ['PY4WEB_DASHBOARD_MODE'] = 'demo' +os.environ['PY4WEB_DASHBOARD_MODE'] = 'none' os.environ['PY4WEB_SERVICE_DB_URI'] = 'sqlite:memory' os.environ['PY4WEB_APPS_FOLDER'] = os.path.join(os.path.dirname(__file__), 'apps') os.environ['PY4WEB_SERVICE_FOLDER'] = os.path.join(os.path.dirname(__file__), 'apps/.service') -Session.SECRET = open(os.path.join(os.path.dirname(__file__), 'apps/.service/session.secret'), 'rb').read() +Session.SECRET = str(uuid.uuid4()) Reloader.import_apps() app = bottle.default_app() diff --git a/deployment_tools/gae/requirements.in b/deployment_tools/gae/requirements.in deleted file mode 100644 index 4602c3cc2..000000000 --- a/deployment_tools/gae/requirements.in +++ /dev/null @@ -1 +0,0 @@ -py4web diff --git a/deployment_tools/ubuntu/machine-setup.sh b/deployment_tools/ubuntu/machine-setup.sh index 1bc82eb27..def1d650d 100755 --- a/deployment_tools/ubuntu/machine-setup.sh +++ b/deployment_tools/ubuntu/machine-setup.sh @@ -7,7 +7,7 @@ # installation script for py4web on Ubuntu server # see https://github.com/web2py/py4web/blob/master/docs/updateDocs.sh # -# tested with Ubuntu Server 20.04.03 LTS +# tested with Ubuntu Server 22.04 LTS # # Usage: # copy and run it in any directory with 'sudo ./machine-setup.sh' @@ -17,8 +17,8 @@ # Parameters: # python_bin is used to state your python version -# by default python_bin=python3.8 -python_bin=python3.8 +# by default python_bin=python3.10 +python_bin=python3.10 # use_iptables is set to yes if # you want to setup linux firewall from scratch @@ -153,11 +153,9 @@ apt-get -y install redis-server echo "=======================================" echo "Installing Python Packages for py4web" +echo "entf server: tornado, gevent, gunicorn" echo "=======================================" cat > requirements-py4web.txt <`__. You could usually find many py4web developers hanging out in the channel. +For quick questions and chats you can also use the free `Discord server dedicated to py4web `__. You could usually find +many py4web developers hanging out in the channel. Tutorials and video @@ -34,16 +38,19 @@ Tutorials and video There are many tutorials and videos available. Here are some of them: - the `Learn Py4Web site `__ by Luca de Alfaro (with lots of excellent training videos) -- the free video `course 2020 by Luca de Alfaro `__ at UC Santa Cruz -- the `py4web blog app `__ by Andrew Gavgavian, which uses py4web to replicate the famous Corey Schafer's tutorial series on creating a blog app in Django -- the `South Breeze Enterprises demo app `__ by `Jim Steil `__. It is built around the structure of the Microsoft Northwind database, +- the free video `course 2020 by Luca de Alfaro `__ + at UC Santa Cruz +- the `py4web blog app `__ by Andrew Gavgavian, which uses py4web to replicate the famous Corey + Schafer's tutorial series on creating a blog app in Django +- the `South Breeze Enterprises demo app `__ by `Jim Steil `__. It is built around + the structure of the Microsoft Northwind database, but converted to SQLite. You can view the final result online `here `__ The sources on GitHub --------------------- -Last but not least, py4web is Open Source, with a BSD v3 license, hosted on GitHub at https://github.com/web2py/py4web. This means that you can read, study and experiment -with all of its internal details by yourself. +Last but not least, py4web is Open Source, with a BSD v3 license, hosted on GitHub at https://github.com/web2py/py4web. This means that you can read, +study and experiment with all of its internal details by yourself. Hints and tips @@ -55,21 +62,22 @@ This paragraph is dedicated to preliminary hints, suggestions and tips that coul Prerequisites ------------- -In order to understand py4web you need at least a basic python knowledge. There are many books, courses and tutorials available on the web - choose what's best for you. -The python's decorators, in particular, are a milestone of any python web framework and you have to fully understand it. +In order to understand py4web you need at least a basic python knowledge. There are many books, courses and tutorials available on the web - choose +what's best for you. The python's decorators, in particular, are a milestone of any python web framework and you have to fully understand it. A modern python workplace ------------------------- -In the following chapters you will start coding on your computer. We suggest you to setup a modern python workplace if you plan to do it efficiently and safely. -Even for running simple examples and experimenting a little, we strongly suggest to use an **Integrated Development Environment** (IDE). This will make your programming experience much better, allowing syntax checking, linting and visual debugging. +In the following chapters you will start coding on your computer. We suggest you to setup a modern python workplace if you plan to do it efficiently +and safely. Even for running simple examples and experimenting a little, we strongly suggest to use an **Integrated Development Environment** (IDE). +This will make your programming experience much better, allowing syntax checking, linting and visual debugging. Nowadays there are two free and multi-platform main choices: Microsoft Visual Studio Code aka `VScode `__ and JetBrains `PyCharm `__. When you'll start to deal with more complex programs and need reliability, we also suggest to: -- use virtual environments (also called **virtualenv**, see +- use virtual environments (also called **virtualenv**\, see `here `__ for an introduction). In a complex workplace this will avoid to be messed up with other python programs and modules @@ -91,14 +99,15 @@ If you have **installed py4web from source**, you just need to open the main py4 "args": ["run", "apps"], "program": "your_full_path_to_py4web.py", -to the vscode ``launch.json`` configuration file. Note that if you're using Windows the "your_full_path_to_py4web.py" parameter must be written using forward slash only, like +to the vscode ``launch.json`` configuration file. Note that if you're using Windows the "your_full_path_to_py4web.py" parameter must be written using +forward slash only, like "C:/Users/your_name/py4web/py4web.py". If you have instead **installed py4web from pip,** you need to: - open the ``apps`` folder with VScode -- copy the standard `py4web.py launcher `__ inside it, but rename it to ``py4web-start.py`` in order to avoid import - errors later: +- copy the standard `py4web.py launcher `__ inside it, but rename it to ``py4web-start.py`` in + order to avoid import errors later: .. code:: python @@ -128,13 +137,14 @@ In PyCharm, if you should get gevent errors you need to enable Settings | Build, How to contribute ================= -We need help from everyone: support our efforts! You can just participate in the Google group trying to answer other's questions, submit bugs using or create pull requests on the GitHub -repository. +We need help from everyone: support our efforts! You can just participate in the Google group trying to answer other's questions, submit bugs using or +create pull requests on the GitHub repository. -If you wish to correct and expand this manual, or even translate it in a new foreign language, you can read all the needed information directly on the -`specific README `__ on GitHub. +If you wish to correct and expand this manual, or even translate it in a new foreign language, you can read all the needed information directly on +the `specific README `__ on GitHub. It's really simple! Just change the .RST files in the /doc folder and create a Pull Request on the GitHub repository at https://github.com/web2py/py4web - you can even do it within your browser. -Once the PR is accepted, your changes will be written on the master branch, and will be reflected on the web pages / pdf / epub at the next output generation on the branch. +Once the PR is accepted, your changes will be written on the master branch, and will be reflected on the web pages / pdf / epub at the next output +generation on the branch. diff --git a/docs/chapter-03.rst b/docs/chapter-03.rst index 205af8151..117d61f69 100644 --- a/docs/chapter-03.rst +++ b/docs/chapter-03.rst @@ -9,8 +9,8 @@ Before everything else it is important to understand that unlike other web frame is not only a python module that can be imported by apps. It is also a program that is in charge of starting some apps. For this reason you need two things: -- the py4web module (which you download from our web site, from pypi, from github) -- one or more folders containing collections of apps you want to run. +- The py4web module (which you download from our web site, from pypi or from github) +- One or more folders containing collections of apps you want to run. py4web has command line options to create a folder with some example apps, to initialize an existing folder, and to add scaffolding apps to that folder. @@ -21,48 +21,69 @@ An apps folder is a python module, and each app is also a python module. Supported platforms and prerequisites ------------------------------------- -PY4WEB runs fine on Windows, MacOS and Linux. Its only prerequisite is +py4web runs fine on Windows, MacOS and Linux. Its only prerequisite is Python 3.7+, which must be installed in advance (except if you use binaries). Setup procedures ---------------- -There are four alternative ways of running py4web, with different level -of difficulty and flexibility. Let’s look at the pros and cons. +There are four alternative ways of installing py4web, we will guide +you through each of them and if you get stuck, reach +`out to us. `__ -Installing from binaries -~~~~~~~~~~~~~~~~~~~~~~~~ -This is not a real installation, because you just copy a bunch of files -on your system without modifying it anyhow. Hence this is the simplest -solution, especially for newbies or students, because it does not -require Python pre-installed on your system nor administrative rights. -On the other hand, it’s experimental, it could contain an old py4web -release and it is quite difficult to add other functionalities to it. +Installing from pip, using a virtual environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In order to use it you just need to download the latest Windows or MacOS -ZIP file from -`this external repository `__. -Unzip it on a local folder and open a command line there. Finally run +A full installation of any complex python application like py4web will +surely modify the python environment of your system. In order to prevent +any unwanted change, it’s a good habit to use a python virtual +environment (also called **virtualenv**, see +`here `__ for an +introduction). This is a standard python feature; if you still don’t +know virtualenv it’s a good time to start its discovery! -:: +Here are the instructions for creating the virtual environment, activating it, +and installing py4web in it: + +.. tabs:: - py4web-start set_password - py4web-start run apps + .. group-tab:: Linux and MacOS -With this type of installation, remember to always use **py4web-start** -instead of ‘py4web’ or ‘py4web.py’ in the following documentation. + :: -Notice the binaries many not correspond to the latest master -or the latest stable branch of py4web although we do our best to -keep them up to date. + python3 -m venv venv + . venv/bin/activate + python -m pip install --upgrade py4web --no-cache-dir + python py4web setup apps + python py4web set_password + python py4web run apps + Starting py4web is same with or without a virtual environment + python py4web run apps -Installing from pip -~~~~~~~~~~~~~~~~~~~ + .. group-tab:: Windows + + :: -Using *pip* is the standard installation procedure for py4web, since it will + run cmd.exe + In e.g. folder c:\py4web + python3 -m venv venv + "C:\py4web\venv\Scripts\activate.bat" + python -m pip install --upgrade py4web --no-cache-dir + cd venv\scripts + py4web.exe setup apps + py4web.exe set_password + py4web.exe run apps + + You can also find power shell scripts in the same folder. Starting py4web is same with or without a virtual environment + python py4web run apps + +Installing from pip, without virtual environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*pip* is the basic installation procedure for py4web, it will quickly install the latest stable release of py4web. From the command line @@ -87,29 +108,14 @@ on any given working folder with If the command py4web is not accepted, it means it’s not in the system’s path. On Windows, a special py4web.exe file (pointing to py4web.py) will be created by *pip* on the system’s path, but not if you type the -*–user* option by mistake. - -Installing using a virtual environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A full installation of any complex python application like py4web will -surely modify the python environment of your system. In order to prevent -any unwanted change, it’s a good habit to use a python virtual -environment (also called **virtualenv**, see -`here `__ for an -introduction). This is a standard python feature; if you still don’t -know virtualenv it’s a good time to start its discovery! - -Here are the instructions for creating the virtual environment, activating it, -and installing py4web in it: +*–user* option by mistake, then you can run the needed commands like this :: - python3 -m venv venv - . venv/bin/activate - python -m pip install --upgrade py4web --no-cache-dir + python3 py4web.py setup apps + python3 py4web.py set_password + python3 py4web.py run apps -The instructions for starting and running py4web are the same with or without a virtual environment. Installing from source (globally) @@ -128,6 +134,8 @@ folder make assets make test make install + py4web setup apps + py4web set_password py4web run apps Also notice that when installing in this way the content of @@ -169,7 +177,7 @@ Once installed, you should always start it from there with: ./py4web.py run apps If you have installed py4web both globally and locally, notice the - **./** ; it forces the run of the local folder’s py4web and not the + **./** ; it forces the run of the local folder's py4web and not the globally installed one. .. group-tab:: Windows @@ -185,7 +193,31 @@ Once installed, you should always start it from there with: But running .py files directly it’s not usual and you’ll need an explicit python3/python command. - +Installing from binaries +~~~~~~~~~~~~~~~~~~~~~~~~ + +This is not a real installation, because you just copy a bunch of files +on your system without modifying it anyhow. Hence this is the simplest +solution, especially for beginners or students, because it does not +require Python pre-installed on your system nor administrative rights. +On the other hand, it’s experimental, it could contain an old py4web +release, DAL support is limited and it is quite difficult to add other functionalities to it. + +In order to use it you just need to download the latest Windows or MacOS +ZIP file from +`this external repository `__. +Unzip it on a local folder and open a command line there. Finally run + +:: + + ./py4web set_password + ./py4web run apps + +(omit './' if you're using Windows). + +Notice: the binaries many not correspond to the latest master +or the latest stable branch of py4web although we do our best to +keep them up to date. Upgrading --------- @@ -204,7 +236,7 @@ If you installed py4web from pip you can simple upgrade it with :: - py4web setup apps + py4web setup in order to re-install them. This is a safety precaution, in case you made changes to those apps. @@ -219,10 +251,7 @@ First run Running py4web using any of the previous procedure should produce an output like this: - -:: - - py4web run apps + .. image:: images/first_run.png :class: with-shadow @@ -245,7 +274,7 @@ two apps in this folder: **Dashboard** (``_dashboard``) and **Default** to avoid conflicts with apps created by you. Once py4web is running you can access a specific app at the following -urls: +urls from the local machine: :: @@ -265,7 +294,7 @@ For all apps the trailing ``/index`` is also optional. .. warning:: For Windows: it could be that ``Ctrl-C`` does not work in order to stop py4web. - In this case, try with ``Ctrl-Break`` or ``Ctrl-Fn-Pause``. + In this case, try with ``Ctrl-Break`` or ``Ctrl-Fn-Pause``\. Command line options @@ -345,38 +374,50 @@ This currently gives an error on binaries installations and from source installa # py4web run -h Usage: py4web.py run [OPTIONS] APPS_FOLDER - Run all the applications on apps_folder + Run the applications on apps_folder Options: - -Y, --yes No prompt, assume yes to questions - [default: False] - - -H, --host TEXT Host name [default: 127.0.0.1] - -P, --port INTEGER Port number [default: 8000] - -p, --password_file TEXT File for the encrypted password [default: - password.txt] - - -s, --server [default|wsgiref|tornado|gunicorn|gevent|waitress| - geventWebSocketServer|wsgirefThreadingServer|rocketServer] - server to use [default: default] - -w, --number_workers INTEGER Number of workers [default: 0] - -d, --dashboard_mode TEXT Dashboard mode: demo, readonly, full, none - [default: full] - - --watch [off|sync|lazy] Watch python changes and reload apps - automatically, modes: off, sync, lazy - [default: lazy] - - --ssl_cert PATH SSL certificate file for HTTPS - --ssl_key PATH SSL key file for HTTPS - --errorlog TEXT Where to send error logs - (:stdout|:stderr|tickets_only|{filename}) - [default: :stderr] - -L, --logging_level INTEGER The log level (0 - 50) [default: 30 - (=WARNING)] - -D, --debug Debug switch [default: False] - -help, -h, --help Show this message and exit. - + -Y, --yes No prompt, assume yes to questions + -H, --host TEXT Host listening IP [default: 127.0.0.1] + -P, --port INTEGER Port number [default: 8000] + -A, --app_names TEXT List of apps to run, comma separated (all if + omitted or empty) + -p, --password_file TEXT File for the encrypted password [default: + password.txt] + -Q, --quiet Suppress server output + -R, --routes Write apps routes to file + -s, --server [default|wsgiref|tornado|wsgiref+threaded|rocket|waitress|gunicorn|gevent|gunicorn+gevent|gevent+websockets] + Web server to use (unavailable: waitress, + gunicorn, gevent, gunicorn+gevent, + gevent+websockets) + -w, --number_workers INTEGER Number of workers [default: 0] + -d, --dashboard_mode TEXT Dashboard mode: demo, readonly, full, none + [default: full] + --watch [off|sync|lazy] Watch python changes and reload apps + automatically, modes: off, sync, lazy + [default: lazy] + --ssl_cert PATH SSL certificate file for HTTPS + --ssl_key PATH SSL key file for HTTPS + --errorlog TEXT Where to send error logs + (:stdout|:stderr|tickets_only|{filename}) + [default: :stderr] + -L, --logging_level INTEGER The log level (0 - 50) [default: 30 + (=WARNING)] + -D, --debug Debug switch + -U, --url_prefix TEXT Prefix to add to all URLs in and out + -m, --mode TEXT default or development [default: default] + -help, -h, --help Show this message and exit. + +The ``app_names`` option lets you filter which specific apps you want to serve (comma separated). If absent or empty +all the apps in the APPS_FOLDER will be run. + +By default (for security reasons) the py4web framework will listen only on 127.0.0.1, i.e. localhost. +If you need to reach it from other machines you must specify the host option, +like ``py4web run --host 0.0.0.0 apps``. + +The ``url_prefix`` option is useful for routing at the py4web level. It allows mapping to multiple versions of py4web +running on different ports as long as the url_prefix and port match the location. For example +``py4web run --url_prefix=/abracadabra --port 8000 apps``. By default py4web will automatically reload an application upon any changes to the python files of that application. The reloading will occur on any first incoming request to the application that has @@ -660,7 +701,8 @@ Deployment on Docker/Podman ~~~~~~~~~~~~~~~~~~~~~~~~~~~ On ``deployment_tools/docker`` there is a simple Dockerfile for quickly running a py4web container. There is also -a docker-compose.yml file for setting up a more complex multi-container with PostgreSQL. +a docker-compose.yml file for setting up a more complex multi-container with PostgreSQL. +A ready docker example based on the Scaffold application can be cloned from this repository Note that you can use them also with Podman, which has the advantage of does not requiring sudo and does not running any background daemon. diff --git a/docs/chapter-05.rst b/docs/chapter-05.rst index b3845cf4c..5ced55be7 100644 --- a/docs/chapter-05.rst +++ b/docs/chapter-05.rst @@ -1,6 +1,6 @@ -======================= -Creating your first app -======================= +=============== +Creating an app +=============== From scratch ------------ @@ -69,11 +69,14 @@ The newly created file will be accessible at Notice that ``static`` is a special path for py4web and only files under the ``static`` folder are served. -Important: internally py4web uses the ombott -(One More BOTTle) `__, -It supports streaming, partial content, range requests, -and if-modified-since. This is all -handled automatically based on the HTTP request headers. +.. important:: + + Internally py4web uses the + `ombott (One More BOTTle) web server `__, + which is a minimal and fast `bottlepy `__ spin-off. + It supports streaming, partial content, range requests, + and if-modified-since. This is all + handled automatically based on the HTTP request headers. Dynamic Web Pages ----------------- @@ -313,32 +316,7 @@ create a new clone of it manually or using the Dashboard. Here is the tree structure of the ``_scaffold`` app: -:: - - ├── __init__.py # imports everything else - ├── common.py # defines useful objects - ├── controllers.py # your actions - ├── databases # your sqlite databases and metadata - │   └── README.md - ├── models.py # your pyDAL table model - ├── settings.py # any settings used by the app - ├── settings_private.py # (optional) settings that you want to keep private - ├── static # static files - │   ├── README.md - │   ├── css # CSS files, we ship bulma because it is JS agnostic - │   │   └── no.css # we used bulma.css in the past - │   ├── favicon.ico - │   └── js # JS files, we ship with these but you can replace them - │   ├── utils.js - ├── tasks.py - ├── templates # your templates go here - │   ├── README.md - │   ├── auth.html # the auth page for register/logic/etc (uses vue) - │   ├── generic.html # a general purpose template - │   ├── index.html - │   └── layout.html # a bulma layout example - └── translations # internationalization/pluralization files go here - └── it.json # py4web internationalization/pluralization files are in JSON, this is an italian example +.. image:: images/scaffold_tree.png The scaffold app contains an example of a more complex action: @@ -358,11 +336,10 @@ The scaffold app contains an example of a more complex action: Notice the following: -- ``request``, ``response``, ``abort`` are defined by - which is a fast bottlepy spin-off. -- ``redirect`` and ``URL`` are similar to their web2py counterparts +- ``request``, ``response``, ``abort`` are defined by ``ombott``. +- ``redirect`` and ``URL`` are similar to their web2py counterparts. - helpers (``A``, ``DIV``, ``SPAN``, ``IMG``, etc) must be imported - from ``yatl.helpers`` . They work pretty much as in web2py + from ``yatl.helpers`` . They work pretty much as in web2py. - ``db``, ``session``, ``T``, ``cache``, ``auth`` are Fixtures. They must be defined in ``common.py``. - ``@action.uses(auth.user)`` indicates that this action expects a @@ -457,3 +434,51 @@ relative to an app. Python files (i.e. "\*.py") in a list passed to the decorator are ignored since they are watched by default. Handler function’s parameter is a list of filepaths that were changed. All exceptions inside handlers are printed in terminal. + +Domain-mapped apps +------------------ + +In production environments it is often required to have several apps being +served by a single py4web server, where different apps are mapped to +different domains. + +py4web can easily handle running multiple apps, but there is no build-in +mechanism for mapping domains to specific applications. Such mapping needs +to be done externally to py4web -- for instance using a web reverse-proxy, +such as nginx. + +While nginx or other reverse-proxies are also useful in production +environments for handling SSL termination, caching and other uses, +we cover only the mapping of domains to py4web applications here. + +An example nginx configuration for an application ``myapp`` mapped to +a domain ``myapp.example.com`` might look like that: + +.. code:: console + + server { + listen 80; + server_name myapp.example.com; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-PY4WEB-APPNAME /myapp; + location / { + proxy_pass http://127.0.0.1:8000/myapp$request_uri; + } + } + +This is an example ``server`` block of nginx configuration. One would have to create +a separate such block for **each app/each domain** being served by py4web server. Note some important aspects: + +- ``server_name`` defines the domain mapped to the app ``myapp``, +- ``proxy_http_version 1.1;`` directive is optional, but highly recommended (otherwise nginx uses HTTP 1.0 to talk + to the backend-server -- here py4web -- and it creates all kinds of issues with buffering and otherwise), +- ``proxy_set_header Host $host;`` directive ensures that the correct ``Host`` is passed to py4web -- here ``myapp.example.com`` +- ``proxy_set_header X-PY4WEB-APPNAME /myapp;`` directive ensures that py4web (and ombott) knows which app to serve + and **also** that this application is domain-mapped -- pay specific attention to the slash (``/``) in front of the ``myapp`` + name -- it is **required** to ensure correct parsing of URLs on ombott level, +- finally ``proxy_pass http://127.0.0.1:8000/myapp$request_uri;`` ensures that the request is passed in its integrity (``$request_uri``) + to py4web server (here: ``127.0.0.1:8000``) and the correct app (``/myapp``). + +Such configuration ensures that all URL manipulation inside ombott and py4web - especially in modules such as ``Auth``, ``Form``, +and ``Grid`` are done correctly using the domain to which the app is mapped to. diff --git a/docs/chapter-06.rst b/docs/chapter-06.rst index e4e4009fe..c4f568771 100644 --- a/docs/chapter-06.rst +++ b/docs/chapter-06.rst @@ -21,7 +21,8 @@ save the session back in the database if data has changed. PY4WEB fixtures provide a mechanism to specify what an action needs so that py4web can accomplish the required tasks (and skip non required ones) in the most efficient manner. Fixtures make the code efficient and -reduce the need for boilerplate code. +reduce the need for boilerplate code. Think of fixtures as per action +(as opposed to per app) middleware. PY4WEB fixtures are similar to WSGI middleware and BottlePy plugin except that they apply to individual actions, not to all of them, and @@ -155,6 +156,7 @@ templates. Here is a simple example: .. code:: python + from py4web.utils.factories import Inject my_var = "Example variable to be passed to a Template" ... @@ -212,6 +214,28 @@ action with a counter that counts “visits”. session['counter'] = counter return str(T("You have been here {n} times").format(n=counter)) + +If the `T` fixture is to be used from inside a template you may want to pass it to the template: + +.. code:: python + + @action('index') + @action.uses("index.html", session, T) + def index(): + return dict(T=T) + +Or perhaps inject (same effect as above) + +.. code:: python + + from py4web.utils.factories import Inject + + @action('index') + @action.uses("index.html", session, Inject(T=T) + def index(): + return dict() + + Now create the following translation file ``translations/en.json``: .. code:: json @@ -257,6 +281,40 @@ Now try create a file called ``translations/it.json`` which contains: Set your browser preference to Italian: now the messages will be automatically translated to Italian. +Notice there is an UI in the Dashboard for creating, updating, and updating translation files. +It can be easily reached via the button ``i18n+p11n``: + +.. image:: images/dashboard_i18n_btn.png + +that leads to the following interface: + +.. image:: images/dashboard_i18n_ui.png + +More details can be found here: https://github.com/web2py/pluralize + + +If you want to force an action to use language defined somewhere else, for example from a session variable, you can do: + +.. code:: python + + @action('index') + @action.uses("index.html", session, T) + def index(): + T.select(session.get("lang", "it")) + return dict(T=T) + +If you want all of your action to use the same pre-defined language and ignore browser preferences, +you have to redefine the select method for the T instance: + +.. code:: python + + T.on_request = lambda *_: T.local.__dict__.update(tag="it", language=T.languages["it"]) + +This is to be done outside any action and will apply to all actions. Action will still need to declare +`action.uses(T)` else the behavior is undefined. + + + The Flash fixture ----------------- @@ -287,13 +345,7 @@ and in the template: .. code:: html - ... -
              - ... - - [[if globals().get('flash'):]] - - [[pass]] + By setting the value of the message in the flash helper, a flash variable is returned by the action and this triggers the JS in the @@ -311,7 +363,7 @@ The client can also set/add flash messages by calling: :: - utils.flash({'message': 'hello world', 'class': 'info'}); + Q.flash({'message': 'hello world', 'class': 'info'}); py4web defaults to an alert class called ``info`` and most CSS frameworks define classes for alerts called ``success``, ``error``, @@ -409,9 +461,14 @@ Client-side session in cookies By default the session object is stored inside a cookie called ``appname_session``. It's a JWT, hence encoded in a URL-friendly string format and signed using the provided secret for preventing tampering. -Notice that it's not encrypted (in fact it's quite trivial to read its -content from http communications or from disk), so do not place any -sensitive information inside, and use a complex secret. + +.. warning:: + + Data embedded in cookies is signed, not encrypted! In fact it's quite + trivial to read its content from http communications or from disk, so + do not place any sensitive information inside, and use a complex secret. + + If the secret changes existing sessions are invalidated. If the user switches from HTTP to HTTPS or vice versa, the user session is also invalidated. Session in cookies have a @@ -512,16 +569,20 @@ inefficient and does not scale well. Sharing sessions ~~~~~~~~~~~~~~~~ -Imagine you have an app "app1" which uses a session and an app "app2" that wants to share a session with app1. Assuming they use sessons in cookies, "app2" would use: +Imagine you have an app "app1" which uses a session and an app "app2" that wants to share a session with app1. Assuming they use sessions in cookies, +"app2" would use: .. code:: python session = Session(secret=settings.SESSION_SECRET_KEY, name="app1_session") -The name tells app2 to use the cookie "app1_session" from app1. Notice it is important that the secret is the same as app1's secret. If using a session in db, then app2 must be using the same db as app1. It is up to the user to make sure that the data stored in the session and shared between the two apps are consistent and we strongly recommend that only app1 writes to the session, unless the share one and the same database. +The name tells app2 to use the cookie "app1_session" from app1. Notice it is important that the secret is the same as app1's secret. If using a session +in db, then app2 must be using the same db as app1. It is up to the user to make sure that the data stored in the session and shared between the two apps +are consistent and we strongly recommend that only app1 writes to the session, unless the share one and the same database. -Notice that it is possible for one app to handle multiple sessions. For example one session may be its own, and another may be used exclusively to read data from another app (app1) running on the same server: +Notice that it is possible for one app to handle multiple sessions. For example one session may be its own, and another may be used exclusively to read +data from another app (app1) running on the same server: .. code:: python @@ -535,7 +596,7 @@ Notice that it is possible for one app to handle multiple sessions. For example The Condition fixture --------------------- -Some times you want to restrict access to an action based on a +Sometimes you want to restrict access to an action based on a given condition. For example to enforce a workflow: .. code:: python @@ -581,22 +642,18 @@ for example, to redirect to another page: Condition(cond, on_false=lambda: redirect(URL('step1'))) -You can use condition to check permissions. For example, assuming you are using -`Tags` as explained in chapter 13 and you are giving group memberships to users, -then you can require that users action have specific group membership: +You can use condition to check permissions. For example, if you +are giving group memberships to users using `Tags` (it will be explained +later on the :ref:`Authorization using Tags` chapter), then you can +require that users action have specific group membership: .. code:: python groups = Tags(db.auth_user) - def requires_membership(group_name): - return Condition( - lambda: group_name in groups.get(auth.user_id), - exception=HTTP(404) - ) - @action("payroll") - @action.uses(auth, requires_membership("employees")) + @action.uses(auth, + Condition(lambda: 'employees' in groups.get(auth.user_id), on_false=lambda: redirect('index'))) def payroll(): return @@ -695,6 +752,23 @@ with the following fields: username, email, password, first_name, last_name, sso_id, and action_token (the last two are mostly for internal use). +If a ``auth_user`` table is defined before calling ``auth.enable()`` +the provided table will be used. + +It is also possible to add ``extra_fields`` to the ``auth_user`` table, +for example: + +.. code:: python + + extra_fields = [ + Field("favorite_color"), + ] + auth = Auth(session, db, extra_fields=extra_fields) + +In any case, we recommend not to pollute the ``auth_user`` table with +extra fields but, instead, to use one of more additional custom +tables that reference users and store the required information. + The ``auth`` object exposes the method:``auth.enable()`` which registers multiple actions including ``{appname}/auth/login``. It requires the presence of the ``auth.html`` template and the diff --git a/docs/chapter-07.rst b/docs/chapter-07.rst index 6fc2b5020..f979e3dc6 100644 --- a/docs/chapter-07.rst +++ b/docs/chapter-07.rst @@ -5,7 +5,7 @@ The Database Abstraction Layer (DAL) DAL introduction ---------------- -py4web rely on a database abstraction layer (DAL), an API that maps +py4web rely on a database abstraction layer (**DAL**), an API that maps Python objects into database objects such as queries, tables, and records. The DAL dynamically generates the SQL in real time using the specified dialect for the database back end, so that you do not have to @@ -16,6 +16,13 @@ The DAL choosen is a pure Python one called `pyDAL >>`` are also directly executable via a py4web shell. -This is a simple example, using the provided ``examples`` app: +This is a simple example, using the provided ``showcase`` app: .. code:: python - >>> from py4web import DAL, Field - >>> from apps.examples import db + >>> from apps.showcase.examples.models import db >>> db.tables() - ['auth_user', 'auth_user_tag_groups', 'person', 'superhero', 'superpower', 'tag', 'product', 'thing'] + ['auth_user', 'auth_user_tag_groups', 'person', 'superhero', 'superpower', 'tag', 'thing', 'user_token', 'dummy'] >>> rows = db(db.superhero.name != None).select() >>> rows.first() , 'name': 'Superman', 'real_identity': 1}> @@ -210,6 +219,24 @@ You can also start by creating a connection from zero. For the sake of simplicit can use SQLite. Nothing in this discussion changes when you switch the back-end engine. +Using the dashboard app with databases +-------------------------------------- + +Generally you can use the dashboard app for viewing and modifying the databases +of a particular app. However this is not bulletproof, so for +security reason this by default is not applied to the showcase app. +But if your installation is local (not exposed to public networks), you can enable it +by simply adding to the file``apps/showcase/__init__.py`` the line: + +.. code:: python + + from .examples.models import db + + +This allow you to look graphically inside the showcase application database: + +.. image:: images/example_db.png + DAL constructor --------------- @@ -321,6 +348,10 @@ Database Connection string - in SQLite the database consists of a single file. If it does not exist, it is created. This file is locked every time it is accessed. + In addition to the file 'storage.sqlite' that contains the data, there will + be also a sql.log file plus one additional file called longhash_tablename.table + for every table definition. The table definition files are used during migrations; + in case of problems they could be deleted (they'll be automatically recreated). - in the case of MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Ingres and Informix the database “test” must be created outside py4web. Once the connection is established, py4web will create, alter, and drop @@ -415,8 +446,8 @@ table is actually referenced. Model-less applications ~~~~~~~~~~~~~~~~~~~~~~~ -In py4web the code defined outside of actions (where normally DAL tables -are defined) is only executed at startup. +Normally in py4web the code that define DAL tables lives in the file +``models.py``, hence it's only executed at startup because it's outside of actions. However, it is possible to define DAL tables on demand inside actions. This is referred to as “model-less” development by the py4web community. @@ -546,15 +577,24 @@ Database folder location ^^^^^^^^^^^^^^^^^^^^^^^^ ``folder`` sets the place where migration files will be created (see -Migrations_ for details). -It is also used for SQLite databases. Automatically set within py4web. -Set a path when using DAL outside py4web. +Migrations_ for details). By default it's automatically set within py4web on the same +folder of the database itself, but you have to specify it when using DAL outside py4web. + +Note that for SQLite databases it's normally necessary, +otherwise you'll implicitly choose an in memory database (where folder and +migrations don't have any sense). So these constructors have the same meaning: + +.. code:: python + + db = DAL('sqlite://storage.sqlite') # folder parameter not specified + db = DAL('sqlite:memory') # in memory database + Default migration settings ^^^^^^^^^^^^^^^^^^^^^^^^^^ The DAL constructor migration settings are booleans affecting defaults -and global behaviour. +and global behaviour (again, see Migrations_ for details) ``migrate = True`` sets default migrate behavior for all tables @@ -574,7 +614,7 @@ operations may be executed immediately, depending on the database engine. If you pass ``db`` in an ``action.uses`` decorator, you don't need to call -commit in the controller, it is done for you. (Also, if you use +commit in the controller, it is automatically done for you (also, if you use ``authenticated`` or ``unauthenticated`` decorator.) .. tip:: @@ -851,7 +891,7 @@ the current application, always set ``migrate=False``. If the legacy table has an auto-increment integer field but it is not called “id”, py4web can still access it but the table definition must declare the auto-increment field with ‘id’ type (that is using -``FIeld('...', 'id')``). +``Field('...', 'id')``). Finally if the legacy table uses a primary key that is not an auto-increment id field it is possible to use a “keyed table”, for @@ -1598,11 +1638,11 @@ works very much like except that it calls the validators for the fields before performing the insert and bails out if the validation does not pass. If validation does -not pass the errors can be found in ``ret.errors``. ``ret.errors`` holds +not pass the errors can be found in ``ret["errors"]``. ``ret["errors"]`` holds a key-value mapping where each key is the field name whose validation failed, and the value of the key is the result from the validation error -(much like ``form.errors``). If it passes, the id of the new record is -in ``ret.id``. Mind that normally validation is done by the form +(much like ``form["errors"]``). If it passes, the id of the new record is +in ``ret["id"]``. Mind that normally validation is done by the form processing logic so this function is rarely needed. Similarly @@ -1619,8 +1659,8 @@ works very much the same as except that it calls the validators for the fields before performing the update. Notice that it only works if query involves a single table. The -number of updated records can be found in ``ret.updated`` and errors -will be in ``ret.errors``. +number of updated records can be found in ``ret["updated"]`` and errors +will be in ``ret["errors"]``. ``drop`` ~~~~~~~~ @@ -1644,9 +1684,9 @@ database. db = DAL("sqlite:memory") db.define_table("thing", Field("name")) - properties = Tags(db.thing) id1 = db.thing.insert(name="chair") id2 = db.thing.insert(name="table") + properties = Tags(db.thing) properties.add(id1, "color/red") properties.add(id1, "style/modern") properties.add(id2, "color/green") @@ -1664,14 +1704,16 @@ database. rows = db(properties.find(["color"])).select() assert len(rows) == 2 -It is internally implemented as a table, which in + +``Tags`` are hierarchical. Then ``find([“color”])`` would return id1 and id2 +because both records have tags with “color”. + +It is internally implemented with the creation of an additional table, which in this example would be db.thing_tags_default, because no tail was -specified on the Tags(table, tail=“default”) constructor. +specified on the ``Tags(table, tail=“default”)`` constructor. -The ``find`` method is doing a search by ``startswith`` of the -parameter. Then find([“color”]) would return id1 and id2 -because both records have tags starting with “color”. py4web uses tags as a -flexible mechanism to manage permissions. +py4web uses ``Tags`` as a flexible mechanism to manage permissions, we'll see +all the details later on the :ref:`Authorization using Tags` chapter. Raw SQL @@ -2340,7 +2382,7 @@ An example use which gives much faster selects is: .. code:: python - rows = db(query).select(cache=(cache.ram, 3600), cacheable=True) + rows = db(query).select(cache=(cache.get, 3600), cacheable=True) Look at `Caching selects`_, to understand what the trade-offs are. @@ -2722,7 +2764,9 @@ Caching selects The select method also takes a ``cache`` argument, which defaults to None. For caching purposes, it should be set to a tuple where the first -element is the cache model (``cache.ram``, ``cache.disk``, etc.), and +element is the cache function with signature `(key, callback, expiration)` +(for example ``cache.get`` assuming ``cache`` +is an instance of the py4web cache object), and the second element is the expiration time in seconds. In the following example, you see a controller that caches a select on @@ -2735,7 +2779,7 @@ the previous data from memory. .. code:: python def cache_db_select(): - logs = db().select(db.log.ALL, cache=(cache.ram, 60)) + logs = db().select(db.log.ALL, cache=(cache.get, 60)) return dict(logs=logs) The ``select`` method has an optional ``cacheable`` argument, normally @@ -2758,7 +2802,7 @@ caching: .. code:: python - rows = db(query).select(cache=(cache.ram, 3600), cacheable=True) + rows = db(query).select(cache=(cache.get, 3600), cacheable=True) Computed and Virtual fields @@ -3303,10 +3347,9 @@ and all owners of Boat: Alex Curt -A lighter alternative to many-to-many relations is tagging, you can -found an example of this in the next section. Tagging works even on -database backends that do not support JOINs like the Google App Engine -NoSQL. +A lighter alternative to many-to-many relations is tagging, see the +:ref:`Authorization using Tags` chapter. Tagging works even on database backends +that do not support JOINs like the Google App Engine NoSQL. Self-Reference and aliases ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3962,7 +4005,7 @@ it to XML/HTML:
              Complete Example Apps
              + Start with a Todo app
              + A minimalist Facebook clone
              + A minimalist Twitter clone
              + More third party contributed apps... +
              Simple Serverside Examples
              example
              Fixtues: FlashFixtures: Flash
              A page with a flash (clientside only)example
              Fixtues: SessionFixtures: Session
              A page with a counter
              If you need to serialize the Rows in any other XML format with custom -tags, you can easily do that using the universal :ref:`TAG` helper +tags, you can easily do that using the universal ``TAG`` XML helper that we'll see later and the Python syntax ``*`` allowed in function calls: @@ -3979,6 +4022,13 @@ that we'll see later and the Python syntax 3Carl +.. warning:: + + Do not confuse the `TAG` XML helper used here (see the :ref:`TAG` + chapter) with the ``Tags`` method that will be extensively explained + on the :ref:`Authorization using Tags` chapter. + + Data representation ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/chapter-12.rst b/docs/chapter-12.rst index 9437a78ad..f08a361d2 100644 --- a/docs/chapter-12.rst +++ b/docs/chapter-12.rst @@ -5,7 +5,68 @@ Forms The Form class provides a high-level API for quickly building CRUD (create, update and delete) forms, especially for working on an existing database table. It can generate and process a form from a list of desired fields and/or from an existing database table. -It is a pretty much equivalent to web2py’s ``SQLFORM``. + +There are 3 types of forms: + +CRUD Create forms: + +.. code:: python + + @action('create_thing') + @action.uses('generic.html', db, flash) + def create_thing(): + form = Form(db.thing) + if form.accepted: + flash.set("record created") + redirect(URL('other_page')) + return locals() + +CRUD Update forms: + +.. code:: python + + @action('update_thing/') + @action.uses('generic.html', db, flash) + def update_thing(thing_id): + form = Form(db.thing, thing_id) + if form.accepted: + flash.set("record updated") + redirect(URL('other_page')) + return locals() + +Non-CRUD forms (not associated to a database): + +.. code:: python + + @action('some_form') + @action.uses('generic.html', flash) + def some_form(): + fields = [ + Field("name", requires=IS_NOT_EMPTY()), + Field("color", required=IS_IN_SET(["red","blue","green"])), + ] + form = Form(fields) + if form.accepted: + flash.set("information recorded") + redirect(URL('other_page')) + return locals() + +The use of flash is optional. ``flash`` is defined in ``common.py`` +in the scaffolding application. It simply stores a message in a cookie +so it can be recovered and displayed after redirection. +This is done in the default layout. + +In this chapter from now on we will assume the following model and +an app derived from the scaffolding app: + +.. code:: python + + db.define_table( + 'thing', + Field('name', requires=IS_NOT_EMPTY()), + Field('color', requires=IS_IN_SET(['red','blue','green'])), + Field('image', 'upload', download_url=lambda name: URL('download', name)), + ) The Form constructor @@ -15,22 +76,22 @@ The ``Form`` constructor accepts the following arguments: .. code:: python - Form(self, - table, - record=None, - readonly=False, - deletable=True, - formstyle=FormStyleDefault, - dbio=True, - keep_values=False, - form_name=False, - hidden=None, - validation=None, - csrf_session=None, - csrf_protection=True, - lifespan=None, - signing_info=None, - ): + Form(self, + table, + record=None, + readonly=False, + deletable=True, + formstyle=FormStyleDefault, + dbio=True, + keep_values=False, + form_name=False, + hidden=None, + validation=None, + csrf_session=None, + csrf_protection=True, + lifespan=None, + signing_info=None, + ): Where: @@ -58,25 +119,25 @@ Create a new minimal app called ``form_minimal`` : .. code:: python - # in form_minimal/__init__.py - from py4web import action, Field, redirect, URL + # in controllers.py + from py4web impot action, redirect, URL, Field from py4web.utils.form import Form - from pydal.validators import IS_NOT_EMPTY - + from pydal.validators import * @action('index', method=['GET', 'POST']) @action.uses('form_minimal.html') def index(): - form = Form([ - Field('product_name'), - Field('product_quantity', 'integer', requires=IS_NOT_EMPTY()), - ]) + fields = [ + Field('name', requires=IS_NOT_EMPTY()), + Field('color', requires=IS_IN_SET(['red','blue','green'])), + ] + form = Form(fields) if form.accepted: - # Do something with form.vars['product_name'] and form.vars['product_quantity'] + # Do something with form.vars['name'] and form.vars['color'] redirect(URL('accepted')) if form.errors: - # display message error - redirect(URL('not_accepted')) + # do something + ... return dict(form=form) @action("accepted") @@ -84,16 +145,12 @@ Create a new minimal app called ``form_minimal`` : return "form_example accepted" - @action("not_accepted") - def not_accepted(): - return "form_example NOT accepted" - - Also, you need to create a file inside the app called ``templates/form_minimal.html`` that just contains the line: .. code:: html + [[extend 'layout.html']] [[=form]] @@ -118,42 +175,27 @@ The next example will. Basic form example ------------------ -In this next basic example we generate a form from a database. +In this next basic example we generate a CRUD create form from a database. Create a new minimal app called ``form_basic`` : .. code-block:: python - # in form_basic/__init__.py - import os - from py4web import action, Field, DAL - from py4web.utils.form import Form, FormStyleDefault - from pydal.validators import IS_NOT_EMPTY, IS_IN_SET - - # database definition - DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases') - if not os.path.isdir(DB_FOLDER): - os.mkdir(DB_FOLDER) - db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER) - db.define_table( - 'person', - Field('superhero', requires=IS_NOT_EMPTY()), - Field('realname'), - Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])), - ) + # in controllers.py + from py4web import action, redirect, URL, Field + from py4web.utils.form import Form + from pydal.validators import * + from .common import db # controllers definition - @action("index", method=["GET", "POST"]) + @action("create_form", method=["GET", "POST"]) @action.uses("form_basic.html", db) - def index(id=None): - form = Form(db.person, id, deletable=False, formstyle=FormStyleDefault) - rows = db(db.person).select() + def create_form(): + form = Form(db.thing) + rows = db(db.thing).select() return dict(form=form, rows=rows) -Because this is a dual purpose form, in case an ``id`` is passed, we also validate it -by checking if the corresponding record exists and raise 404 if not. - Note the import of two simple validators on top, in order to be used later with the ``requires`` parameter. We'll fully explain them on the :ref:`Form validation` paragraph. @@ -163,7 +205,9 @@ contains, for example, the following code: .. code:: html -

              Form Basic example: Superhero Identity

              + [[extend "layout.html"]] + +

              Form Basic example: My Things

              [[=form]] @@ -171,12 +215,12 @@ contains, for example, the following code:
                [[for row in rows:]] -
              • [[=row.id]]: [[=row.superhero]] ([[=row.realname]]) from [[=row.universe]]
              • +
              • [[=row.id]]: [[=row.name]] has color [[=row.color]]
              • [[pass]]
              - -Reload py4web and visit http://127.0.0.1:8000/form_basic : + +Reload py4web and visit http://127.0.0.1:8000/create_form : the result is an input form on the top of the page, and the list of all the previously added entries on the bottom: @@ -185,58 +229,45 @@ previously added entries on the bottom: This is a simple example and you cannot change nor delete existing records. But if you'd like to experiment, the database content can be fully seen and changed with the Dashboard app. +You can turn a create form into a CRUD update form by passing a record or a record id +it second argument: -Notice that py4web by default let you choose the value of the `universe` field using -a dropdown menu: - -.. image:: images/form3.png - -The basic form usage is quite useful for rapid prototyping of programs, since you don't need -to specify the layout of the form. On the other hand, you cannot change its default behaviour. +.. code:: html + # controllers definition + @action("update_form/", method=["GET", "POST"]) + @action.uses("form_basic.html", db) + def update_form(): + form = Form(db.thing, thing_id) + rows = db(db.thing).select() + return dict(form=form, rows=rows) File upload field ~~~~~~~~~~~~~~~~~ -The file upload field is quite particular. The standard way to use it (as in the _scaffold app) -is to have the UPLOAD_FOLDER defined in the common.py file. But if you don't specify it, then the -default value of ``your_app/upload`` folder will be used (and the folder will also be created if needed). -Let's look at a simple example: - -.. code-block:: python +We can make a minor modification to our reference model and an upload type file: - # in form_upload/__init__.py - import os - from py4web.core import required_folder - from py4web import action, Field, DAL - from py4web.utils.form import Form, FormStyleDefault - from pydal.validators import IS_NOT_EMPTY - - # database definition - DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases') - if not os.path.isdir(DB_FOLDER): - os.mkdir(DB_FOLDER) - db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER) - db.define_table( - 'person', - Field('superhero', requires=IS_NOT_EMPTY()), - Field('image', "upload", label='Superhero Image', requires=IS_NOT_EMPTY()), - ) +.. code:: python - @action("index", method=["GET", "POST"]) - @action.uses("form_upload.html", db) - def upload(id=None): - form = Form(db.person, id, deletable=False, formstyle=FormStyleDefault) - rows = db(db.person).select() - return dict(form=form, rows=rows) + db.define_table( + 'thing', + Field('name', requires=IS_NOT_EMPTY()), + Field('color', requires=IS_IN_SET(['red','blue','green'])), + Field('image', 'upload', download_url=lambda image: URL('download', image)), + ) +The file upload field is quite particular. The standard way to use it (as in the _scaffold app) +is to have the UPLOAD_FOLDER defined in the common.py file. But if you don't specify it, then the +default value of ``your_app/upload`` folder will be used (and the folder will also be created if needed). +``download_url`` is a callback that given the image name, generated the URL to download. The ``download`` +url is predefined in ``common.py``. -And in templates/form_upload.html : +We can modify ``form_basic.html`` to display the uploaded images: .. code:: html -

              Form upload example: Superhero Identity

              +

              Form upload example: My Things

              [[=form]] @@ -244,17 +275,13 @@ And in templates/form_upload.html :
                [[for row in rows:]] -
              • [[=row.id]]: [[=row.superhero]] = [[=row.image]]
              • +
              • [[=row.id]]: [[=row.name]] has color [[=row.color]] + [[pass]]
              -This gives a result like the following: - -.. image:: images/form6.png - - -Note that the uploaded files will be saved on the UPLOAD_FOLDER folder with their name hashed. +The uploaded files (the thing images) are saved on the UPLOAD_FOLDER folder with their name hashed. Other details on the upload fields can be found on :ref:`Field constructor` paragraph, including a way to save the files inside the database itself. @@ -286,69 +313,36 @@ This is an improved 'Basic Form Example' with a radio button widget: .. code:: python - # in form_widgets/__init__.py - import os - from py4web import action, Field, DAL + # in controllers.py + from py4web import action, redirect, URL, Field from py4web.utils.form import Form, FormStyleDefault, RadioWidget - from pydal.validators import IS_NOT_EMPTY, IS_IN_SET - - # database definition - DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases') - if not os.path.isdir(DB_FOLDER): - os.mkdir(DB_FOLDER) - db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER) - db.define_table( - 'person', - Field('superhero', requires=IS_NOT_EMPTY()), - Field('realname'), - Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])), - ) + from pydal.validators import * + from .common import db # controllers definition - @action("index", method=["GET", "POST"]) + @action("create_form", method=["GET", "POST"]) @action.uses("form_widgets.html", db) - def index(id=None): - FormStyleDefault.widgets['universe']=RadioWidget() - form = Form(db.person, id, deletable=False, formstyle=FormStyleDefault) - rows = db(db.person).select() + def create_form(): + FormStyleDefault.widgets['color']=RadioWidget() + form = Form(db.thing, formstyle=FormStyleDefault) + rows = db(db.thing).select() return dict(form=form, rows=rows) Notice the differences from the 'Basic Form example' we've seen at the beginning of the chapter: - you need to import the widget from the py4web.utils.form library -- before the form definition, you define the ``universe`` field form style with the line: +- before the form definition, you define the ``color`` field form style with the line: .. code:: python - FormStyleDefault.widgets['universe']=RadioWidget() - -You will also need a template file ``templates/form_widgets.html`` that -contains the following code (as the form_basic.html) : - -.. code:: html - -

              Form Widget example: Superhero Identity

              - - [[=form]] - -

              Rows

              - -
                - [[for row in rows:]] -
              • [[=row.id]]: [[=row.superhero]] ([[=row.realname]]) from [[=row.universe]]
              • - [[pass]] -
              + FormStyleDefault.widgets['color']=RadioWidget() The result is the same as before, but now we have a radio button widget instead of the dropdown menu! -.. image:: images/form4.png - - Using widgets in forms is quite easy, and they'll let you have more control on its pieces. - Custom widgets ~~~~~~~~~~~~~~ @@ -357,26 +351,11 @@ improving again our Superhero example: .. code:: python - # - # in form_custom_widgets/__init__.py - # - import os - from py4web import action, Field, DAL + # in controllers.py + from py4web import action, redirect, URL, Field from py4web.utils.form import Form, FormStyleDefault, RadioWidget - from pydal.validators import IS_NOT_EMPTY, IS_IN_SET - from yatl.helpers import INPUT, DIV - - # database definition - DB_FOLDER = os.path.join(os.path.dirname(__file__), 'databases') - if not os.path.isdir(DB_FOLDER): - os.mkdir(DB_FOLDER) - db = DAL('sqlite://storage.sqlite', folder=DB_FOLDER) - db.define_table( - 'person', - Field('superhero', requires=IS_NOT_EMPTY()), - Field('realname'), - Field('universe', requires=IS_IN_SET(['DC Comics','Marvel Comics'])), - ) + from pydal.validators import * + from .common import db # custom widget class definition class MyCustomWidget: @@ -395,49 +374,24 @@ improving again our Superhero example: return control # controllers definition - @action("index", method=["GET", "POST"]) + @action("create_form", method=["GET", "POST"]) @action.uses("form_custom_widgets.html", db) - def index(id=None): + def create_form(): MyStyle = FormStyleDefault MyStyle.classes = FormStyleDefault.classes - MyStyle.widgets['superhero']=MyCustomWidget() - MyStyle.widgets['realname']=MyCustomWidget() - MyStyle.widgets['universe']=RadioWidget() + MyStyle.widgets['name']=MyCustomWidget() + MyStyle.widgets['color']=RadioWidget() - form = Form(db.person, id, deletable=False, formstyle=MyStyle) - rows = db(db.person).select() + form = Form(db.thing, deletable=False, formstyle=MyStyle) + rows = db(db.thing).select() return dict(form=form, rows=rows) - - -You will also need a template file ``templates/form_custom_widgets.html`` that -contains the following code (as the form_basic.html) : - -.. code:: html - -

              Form Custom Widgets example: Superhero Identity

              - - [[=form]] - -

              Rows

              - -
                - [[for row in rows:]] -
              • [[=row.id]]: [[=row.superhero]] ([[=row.realname]]) from [[=row.universe]]
              • - [[pass]] -
              - - - The result is similar to the previous ones, but now we have a custom input field, -with foreground color red and background color black: - -.. image:: images/form5.png +with foreground color red and background color black, Even the radio button widget has changed, from red to blue. - Advanced form design -------------------- @@ -476,6 +430,7 @@ For example you could use it to avoid displaying the ``id`` field while editing .. code:: html + [[extend 'layout.html']] [[=form.custom.begin ]] [[for field in DETAIL_FIELDS: ]] [[ if field not in ['id']: ]] @@ -487,10 +442,6 @@ For example you could use it to avoid displaying the ``id`` field while editing [[=form.custom.submit ]] [[=form.custom.end ]] - -Custom forms are also frequently used for avoiding code redundancy; you can use a single template file for -multiple form types, and programmatically change the fields contained and how to render them. - Note: 'custom' is just a convention, it could be any name that does not clash with already defined objects. .. warning:: @@ -504,6 +455,33 @@ Note: 'custom' is just a convention, it could be any name that does not clash wi your css framework, you'll have to add an outer DIV in order to get select controls to appear correctly. + +You can also be more creative and use your HTML in the template instead of using widgets: + +.. code:: css + + [[extend 'layout.html']] + + [[for field, error form.errors.items:]] +
              Field [[=field]] [[=error]]
              + [[pass]] + + [[=form.custom.begin ]] + +
              + +
              +
              + [[for color in ['red', 'blue', 'green']:]] + + + [[pass]] +
              + + [[=form.custom.end ]] + The sidecar parameter ~~~~~~~~~~~~~~~~~~~~~ @@ -828,6 +806,19 @@ Examples: prepend_scheme='https') +``IS_SAFE`` +^^^^^^^^^^^ + +.. code:: python + + requires = IS_SAFE(error_message='Unsafe Content') + requires = IS_SAFE(mode="sanitize") + requires = IS_SAFE(sanitizer=lambda text: str(XML(text, sanitize=True))) + +This validators is for text fields that should contain HTML and may contain invalid tags (script, ember, object, iframe). +It works by trying to sanitize the content and either provide an error (mode="error") or replacing the content +with the sanitized one (mode="sanitize"). You can specify the error message, the mode, and provide your own sanitizer. + ``IS_SLUG`` ^^^^^^^^^^^ @@ -1131,7 +1122,7 @@ To keep the options alphabetically sorted by their labels into the drop down lis ^^^^^^^^^^^^^^^^^^^^^^^^^ The ``IS_IN_SET`` validator has an optional attribute ``multiple=False``. If set to True, multiple values can be stored in one -field. The field should be of type ``list:integer`` or ``list:string`` as discussed in [[Chapter 6 ../06#list-type-and-contains]]. +field. The field should be of type ``list:integer`` or ``list:string`` as discussed in :ref:`list_type and contains`. An explicit example of tagging is discussed there. We strongly suggest using the jQuery multiselect plugin to render multiple fields. **Note** that when ``multiple=True``, ``IS_IN_SET`` will accept zero or more values, i.e. it will accept the field when nothing has been selected. @@ -1823,19 +1814,14 @@ Here is an example: from py4web.utils.form import Form, FormStyleBulma from pydal.validators import IS_INT_IN_RANGE - def check_nonnegative_quantity(form): - if not form.errors and form.vars['product_quantity'] % 2: - form.errors['product_quantity'] = T('The product quantity must be even') + def custom_check(form): + if not 'name' in form.errors and len(form.vars['name']) < 4 + form.errors['name'] = T("too short") @action('form_example', method=['GET', 'POST']) @action.uses('form_example.html', session) def form_example(): - form = Form([ - Field('product_name'), - Field('product_quantity', 'integer', requires=IS_INT_IN_RANGE(0,100))], - validation=check_nonnegative_quantity, - formstyle=FormStyleBulma) + form = Form(db.thing, validation=custom_check) if form.accepted: - # Do something with form.vars['product_name'] and form.vars['product_quantity'] redirect(URL('index')) return dict(form=form) diff --git a/docs/chapter-13.rst b/docs/chapter-13.rst index 22062f11e..f0cf48949 100644 --- a/docs/chapter-13.rst +++ b/docs/chapter-13.rst @@ -145,7 +145,7 @@ The second one forces the login if needed: @action.uses(auth.user) def index(): user = auth.get_user() - return 'hello {first_name}'.format(**user)' + return 'hello {first_name}'.format(**user) Here ``@action.uses(auth.user)`` tells py4web that this action requires a logged in user and should redirect to login if no user is logged in. @@ -153,21 +153,29 @@ a logged in user and should redirect to login if no user is logged in. Two Factor Authentication ~~~~~~~~~~~~~~~~~~~~~~~~~ -Two factor authentication (or Two-step verification) is a way of improving authentication security. When activated an extra step is added in the login process. In the first step, users are shown the standard username/password form. If they successfully pass this challenge by submitting the correct username and password, and two factor authentication is enabled for the user, the server will present a second form before logging them in. +Two factor authentication (or Two-step verification) is a way of improving authentication security. +When activated an extra step is added in the login process. In the first step, users are shown the +standard username/password form. If they successfully pass this challenge by submitting the correct +username and password, and two factor authentication is enabled for the user, the server will +present a second form before logging them in. There are a few Auth settings available to control how two factor authentication works. -The follow can be specified on Auth instantiation: +The following can be specified on Auth instantiation: -- two_factor_required -- two_factor_send +- ``two_factor_required`` +- ``two_factor_send`` +- ``two_factor_validate`` two_factor_required ^^^^^^^^^^^^^^^^^^^ -When you pass a method name to the two_factor_filter parameter you are telling py4web to call that method to determine whether or not this login should be use or bypass two factor authentication. If your method returns True, then this login requires two factor. If it returns False, two factor authentication is bypassed for this login. +When you pass a method name to the ``two_factor_required`` parameter you are telling py4web to call +that method to determine whether or not this login should be use or bypass two factor authentication. +If your method returns True, then this login requires two factor. If it returns False, two factor authentication +is bypassed for this login. -Sample two_factor_filter method +Sample ``two_factor_required`` method This example shows how to allow users that are on a specific network. @@ -191,7 +199,9 @@ This example shows how to allow users that are on a specific network. two_factor_send ^^^^^^^^^^^^^^^ -When two factor authentication is active, py4web generates a 6 digit code (using random.randint) and sends it to you. How this code is sent, is up to you. The two_factor_send argument to the Auth class allows you to specify the method that sends the two factor code to the user. +When two factor authentication is active, py4web can generate a 6 digit code (using random.randint) and +makes it possible to send it to the user. How this code is sent, is up to you. The ``two_factor_send`` +argument to the Auth class allows you to specify the method that sends the two factor code to the user. This example shows how to send an email with the two factor code: @@ -209,7 +219,7 @@ This example shows how to send an email with the two factor code: print(e) return code -Notice that this method takes to arguments: the current user, and the code to be sent. +Notice that this method takes two arguments: the current user, and the code to be sent. Also notice this method can override the code and return a new one. .. code:: python @@ -217,6 +227,62 @@ Also notice this method can override the code and return a new one. auth.param.two_factor_required = user_outside_network auth.param.two_factor_send = send_two_factor_email +two_factor_validate +^^^^^^^^^^^^^^^^^^^ + +By default, py4web will validate the user input in the two factor form by comparing the code entered +by the user with the code generated and sent using ``two_factor_send``. However, sometimes it may be +useful to define a custom validation of this user-entered code. For instance, if one would like to use the +TOTP (or the Time-Based One-Time-Passwords) as the two factor authentication method, the validation +requires comparing the code entered by the user with the value generated at the same time at the server side. +Hence, it is not sufficient to generate that value earlier when showing the form (using for instance +``two_factor_send`` method), because by the time the user submits the form, the current valid value may +already be different. Instead, this value should be +generated when validating the form submitted by the user. + +To accomplish such custom validation, the ``two_factor_validate`` method is available. It takes two arguments: + + - the current user + - the code that was entered by the user into the two factor authentication form + +The primary use-case for this method is validation of time-based passwords. + +This example shows how to validate a time-based two factor code: + +.. code:: python + + def validate_code(user, code): + try: + # get the correct code from an external function + correct_code = generate_time_based_code(user_id) + except Exception as e: + # return None to indicate that validation could not be performed + return None + + # compare the value entered in the auth form with the correct code + if code == correct_code: + return True + else: + return False + +The ``validate_code`` method must return one of three values: + +- ``True`` - if the validation succeeded, +- ``False`` - if the validation failed, +- ``None`` - if the validation was not possible for any reason + +Notice that - if defined - this method is _always_ called to validate the two factor +authentication form. It is up to you to decide what kind of validation it does. If the returned value is ``True``, +the user input will be accepted as valid. If the returned value is ``False`` then the user input will be +rejected as invalid, number of tries will be decreased by one, and user will be asked to try again. +If the returned value is ``None`` the user input will be checked against the code generated with the use +of ``two_factor_send`` method and the final result will depend on that comparison. In this case authentication +will fail if ``two_factor_send`` method was not defined, and hence no code was sent to the user. + +.. code:: python + + auth.param.two_factor_validate = validate_code + two_factor_tries ^^^^^^^^^^^^^^^^ @@ -231,11 +297,15 @@ Once this is all setup, the flow for two factor authentication is: - present the login page - upon successful login and user passes two_factor_required - redirect to py4web auth/two_factor endpoint - - generate 6 digit verification code - - call two_factor_send to send the verification code to the user + - if ``two_factor_send`` method has been defined: + - generate 6 digit verification code + - call ``two_factor_send`` to send the verification code to the user - display verification page where user can enter their code + - if ``two_factor_validate`` method has been defined - call it to validate the user-entered code - upon successful verification, take user to _next_url that was passed to the login page +Important! If you filtered ``ALLOWED_ACTIONS`` in your app, make sure to whitelist the "two_factor" action +so not to block the two factor API. Auth Plugins @@ -350,7 +420,8 @@ Authorization using Tags As already mentioned, authorization is the process of verifying what specific applications, files, and data a user has access to. This is accomplished -in py4web using ``Tags``. +in py4web using ``Tags``, that we've already discovered on :ref:`Tagging records` +in the DAL chapter. Tags and Permissions @@ -376,14 +447,16 @@ from ``pydal.tools``. Then create a Tags object to tag a table: .. code:: python from pydal.tools.tags import Tags - groups = Tags(db.auth_user) + groups = Tags(db.auth_user, 'groups') -If you look at the database level, a new table will be created with a -name equals to tagged_db + '_tag' + tagged_name, in this case -``auth_user_tag_groups``: +The tail_name parameter is optional and if not specified the 'default' +value will be used. If you look at the database level, a new table will +be created with a name equals to ``tagged_db + '_tag_' + tail_name``, +in this case ``auth_user_tag_groups``: .. image:: images/tags_db.png + Then you can add one or more tags to records of the table as well as remove existing tags: @@ -428,6 +501,27 @@ tag(s): users = db(groups.find([group_name])).select(orderby=db.auth_user.first_name | db.auth_user.last_name) return {'users': users} +We've already seen a simple ``requires_membership`` fixture on :ref:``The Condition fixture``. It +enables the following syntax: + +.. code:: python + + groups = Tags(db.auth_user) + + class requires_membership(Fixture): + def __init__(self, group): + self.__prerequisites__ = [auth.user] # you must have a user before you can check + self.group = group # store the group when action defined + def on_request(self, context): # will be called if the action is called + if self.group not in groups.get(auth.user_id): + raise HTTP(401) # check and do something + + @action('index') + @action.uses(requires_membership('teacher')) + def index(): + return 'hello teacher' + + We leave it to you as an exercise to create a fixture ``has_membership`` to enable the following syntax: @@ -500,3 +594,29 @@ Notice here ``permissions.find(permission)`` generates a query for all groups with the permission and we further filter those groups for those the current user is member of. We count them and if we find any, then the user has the permission. + +User Impersonation +~~~~~~~~~~~~~~~~~~ + +Auth provides API that allow you to impersonate another user. +Here is an example of an action to start impersonating and stop impersonating another user. + +.. code:: python + + @action("impersonate/{user_id:int}", method="GET") + @action.uses(auth.user) + def start_impersonating(user_id): + if (not auth.is_impersonating() and + user_id and + user_id != auth.user_id and + db(db.auth_user.id==user_id).count()): + auth.start_impersonating(user_id, URL("index")) + raise HTTP(404) + + @action("stop_impersonating", method="GET") + @action.uses(auth) + def stop_impersonating(): + if auth and auth.is_impersonating(): + auth.stop_impersonating(URL("index")) + redirect(URL("index")) + diff --git a/docs/chapter-14.rst b/docs/chapter-14.rst index a374d46ee..a80d87fdb 100644 --- a/docs/chapter-14.rst +++ b/docs/chapter-14.rst @@ -38,8 +38,8 @@ Create a new minimal app called ``grid``. Change it with the following content. # in grid/__init__.py import os from py4web import action, Field, DAL - from py4web.utils.grid import Grid, GridClassStyleBulma - from py4web.utils.form import Form, FormStyleBulma + from py4web.utils.grid import * + from py4web.utils.form import * from yatl.helpers import A @@ -583,4 +583,41 @@ combine fields to be displayed together as the filter_out method would You need to determine which method is best for your use case understanding the different grids in the same application may need to -behave differently. \ No newline at end of file +behave differently. + + +Grids with checkboxes +--------------------- + +While the grid, per se, does not support checkboxes, you can use custom columns to add one or more columns of checkboxes. +You can also add the helpers logic (the grid uses helpers to generate HTML) to wrap it in a ```` and add one +or more submit buttons. You can then add logic to process the selected rows when the button is selected. For example: + +.. code:: python + + column = Column("select", lambda row: INPUT(_type="checkbox",_name="selected_id",_value=row.id)) + + @action("manage") + @action("manage/") + @action.uses("manage.html", db) + def manage(path=None): + + grid = Grid(path, db.thing, columns=[column, db.thing.name]) + + # if we are displaying a "select" grid page (not a form) + if not grid.form: + grid = grid.render() + # if checkboxes selection was submitted + if request.method == "POST": + # do something with the selected ids + print("you selected", request.POST.get("selected_id")) + # inject a ```` and a ``submit`` button + grid.children[1:] = [FORM( + *grid.children[1:], + DIV(INPUT(_type="submit",_value="do it!")), + _method="POST", + _action=request.url)] + return locals() + +Notice in the above example ``request.POST.get("selected_id")`` can be a single ID (if one selected) or a list of IDs (if more than one +is selected). diff --git a/docs/chapter-16.rst b/docs/chapter-16.rst index 55bc63e27..c14618102 100644 --- a/docs/chapter-16.rst +++ b/docs/chapter-16.rst @@ -2,6 +2,155 @@ Advanced topics and examples ============================ +The scheduler +------------- + +Py4web has a built-in scheduler. There is nothing for you to install or configure to make it work. + +Given a task (just a python function), you can schedule async runs of that function. +The runs can be a one-off or periodic. They can have timeout. They can be scheduled to run at a given scheduled time. + +The scheduler works by creating a table ``task_run`` and enqueueing runs of the predefined task as table records. +Each ``task_run`` references a task and contains the input to be passed to that task. The scheduler will capture the +task stdout+stderr in a ``db.task_run.log`` and the task output in ``db.task_run.output``. + +A py4web thread loops and finds the next task that needs to be executed. For each task it creates a worker process +and assigns the task to the worker process. You can specify how many worker processes should run concurrently. +The worker processes are daemons and they only live for the life of one task run. Each worker process is only +responsible for executing that one task in isolation. The main loop is responsible for assigning tasks and timeouts. + +The system is very robust because the only source of truth is the database and its integrity is guaranteed by +transactional safety. Even if py4web is killed, running tasks continue to run unless they complete, fail, or are +explicitly killed. + +Aside for allowing multiple concurrent task runs in execution on one node, +it is also possible to run multiple instances of the scheduler on different computing nodes, +as long as they use the same client/server database for ``task_run`` and as long as +they all define the same tasks. + +Here is an example of how to use the scheduler: + +.. code:: python + + from pydal.tools.scheduler import Scheduler, delta, now + from .common import db + + # create and start the scheduler + scheduler = Scheduler(db, sleep_time=1, max_concurrent_runs=1) + scheduler.start() + + # register your tasks + scheduler.register_task("hello", lambda **inputs: print("hi!")) + scheduler.register_task("slow", lambda: time.sleep(10)) + scheduler.register_task("periodic", lambda **inputs: print("I am periodic!")) + scheduler.register_task("fail", lambda x: 1 / x) + + # enqueue some task runs: + + scheduler.enqueue_run(name="hello") + scheduler.enqueue_run(name="hello", scheduled_for=now() + delta(10) # start in 10 secs + scheduler.enqueue_run(name="slow", timeout=1) # 1 secs + scheduler.enqueue_run(name="periodic", period=10) # 10 secs + scheduler.enqueue_run(name="fail", inputs={"x": 0}) + +Notice that in scaffolding app, the scheduler is created and started in common if +``USE_SCHEDULER=True`` in ``settings.py``. + +You can manage your task runs busing the dashboard or using a ``Grid(path, db.task_run)``. + +To prevent database locks (in particular with sqlite) we recommend: + +- Use a different database for the scheduler and everything else +- Always ``db.commit()`` as soon as possible after any insert/update/delete +- wrap your database logic in tasks in a try...except as in + +.. code:: python + + def my_task(): + try: + # do something + db.commit() + except Exception: + db.rollback() + + +Sending messages using a background task +---------------------------------------- + +As en example of application of the above, consider the case of wanting to send emails asynchronously from a background task. +In this example we send them using SendGrid from Twilio (https://www.twilio.com/docs/sendgrid/for-developers/sending-email/quickstart-python). + +Here is a possible scheduler task to send the email: + +.. code:: python + + import sendgrid + from sendgrid.helpers.mail import Mail, Email, To, Content + + def sendmail_task(from_addr, to_addrs, subject, body): + "" + # build the messages using sendgrid API + from_email = Email(from_addr) # Must be your verified sender + content_type = "text/plain" if body[:6] != "" else "text/html" + content = Content(content_type, body) + mail = Mail(from_email, To(to_addrs), subject, content) + # ask sendgrid to deliver it + sg = sendgrid.SendGridAPIClient(api_key=settings.SENDGRID_API_KEY) + response = sg.client.mail.send.post(request_body=mail.get()) + # check if worked + assert response.status_code == "200" + + # register the above task with the scheduler + scheduler.register_task("sendmail", sendmail_task) + + +To schedule sending a new email do: + +.. code:: python + + email = { + "from_addr": "me@example.com", + "to_addrs": ["me@example.com"], + "subject": "Hello World", + "body": "I am alive!", + } + scheduler.enqueue_run(name="sendmail", inputs=email, scheduled_for=None) + +The key:value in the email representation must match the arguments of the task. +The ``scheduled_for`` argument is optional and allows you to specify when the email should be sent. +You can use the Dashboard to see the status of your ``task_run``\s for the task called ``sendmail``. + +You can also tell auth to tap into above mechanism for sending emails: + +.. code:: python + + class MySendGridSender: + def __init__(self, from_addr): + self.from_addr = from_adds + def send(self, to_addr, subject, body): + email = { + "from_addr": self.from_addr, + "to_addrs": [to_addr], + "subject": subject, + "body": body, + } + scheduler.enqueue_run(name="sendmail", inputs=email) + + auth.sender = MySendGridSender(from_addr="me@example.com") + +With the above, Auth will not send emails using smtplib. Instead it will send them with SendGrid using the scheduler. +Notice the only requirement here is that ``auth.sender`` must be an object with a ``send`` method with the same signature as in the example. + +Notice, it it also possible to send SMS messages instead of emails but this requires 1) store the phone number in ``auth_user`` and 2) override the ``Auth.send`` method. + + +Celery +------ + +Yes. You can use Celery instead of the build-in scheduler but it adds complexity and it is less robust. +Yet the build-in scheduler is designed for long running tasks and the database can become a bottleneck +if you have hundreds of tasks running concurrently. Celery may work better if you have more than 100 concurrent +tasks and/or they are short running tasks. py4web and asyncio diff --git a/docs/conf.py b/docs/conf.py index b3bdb2337..99fc5e9aa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,6 +31,8 @@ if '__version__ = ' in line: values = line.split(sep = ' = ') current_version = values[1].strip('\n').strip('"') + current_version = current_version[2:] + current_version = current_version[:-2] break release = current_version version = current_version @@ -46,7 +48,7 @@ 'sphinx.ext.githubpages', 'sphinx.ext.autosectionlabel', 'sphinx_tabs.tabs', - 'sphinxcontrib.spelling', +# 'sphinxcontrib.spelling', ] # Add any paths that contain templates here, relative to this directory. @@ -82,7 +84,6 @@ html_favicon = 'images/logo-32x32.ico' html_theme_options = { 'logo_only': False, - 'display_version': True, } # The master toctree document. master_doc = 'index' @@ -171,11 +172,12 @@ # -- Options for PDF output -------------------------------------------------- # settings for creating PDF with rinoh -rinoh_documents = [( - master_doc, - 'target', - project + ' Documentation', - '© ' + copyright, +rinoh_documents = [dict( + doc = master_doc, + target = 'target', + title = project + ' Documentation', + date = '© ' + copyright, + logo = 'images/logo.png' )] @@ -185,4 +187,4 @@ # ---- Options for spelling ------------------------------------------------- spelling_lang='en_US' -spelling_word_list_filename='spelling_wordlist_en.txt' \ No newline at end of file +spelling_word_list_filename='spelling_wordlist_en.txt' diff --git a/docs/images/dashboard_i18n_btn.png b/docs/images/dashboard_i18n_btn.png new file mode 100755 index 000000000..f32ba92a7 Binary files /dev/null and b/docs/images/dashboard_i18n_btn.png differ diff --git a/docs/images/dashboard_i18n_ui.png b/docs/images/dashboard_i18n_ui.png new file mode 100644 index 000000000..b71a21bd5 Binary files /dev/null and b/docs/images/dashboard_i18n_ui.png differ diff --git a/docs/images/example_db.png b/docs/images/example_db.png new file mode 100644 index 000000000..7ae7315ba Binary files /dev/null and b/docs/images/example_db.png differ diff --git a/docs/images/first_run.png b/docs/images/first_run.png index 59320040c..a670f0150 100644 Binary files a/docs/images/first_run.png and b/docs/images/first_run.png differ diff --git a/docs/images/form1.png b/docs/images/form1.png index 6f66b7257..4839a27f9 100755 Binary files a/docs/images/form1.png and b/docs/images/form1.png differ diff --git a/docs/images/form2.png b/docs/images/form2.png index b83647c2f..642631948 100755 Binary files a/docs/images/form2.png and b/docs/images/form2.png differ diff --git a/docs/images/form3.png b/docs/images/form3.png deleted file mode 100644 index 3de9bbba9..000000000 Binary files a/docs/images/form3.png and /dev/null differ diff --git a/docs/images/form4.png b/docs/images/form4.png deleted file mode 100644 index 9534e4683..000000000 Binary files a/docs/images/form4.png and /dev/null differ diff --git a/docs/images/icon-gear.png b/docs/images/icon-gear.png new file mode 100644 index 000000000..a6bcc602a Binary files /dev/null and b/docs/images/icon-gear.png differ diff --git a/docs/images/icon-lens.png b/docs/images/icon-lens.png new file mode 100644 index 000000000..632f1511c Binary files /dev/null and b/docs/images/icon-lens.png differ diff --git a/docs/images/icon-start.png b/docs/images/icon-start.png new file mode 100644 index 000000000..426638c71 Binary files /dev/null and b/docs/images/icon-start.png differ diff --git a/docs/images/icon-stop.png b/docs/images/icon-stop.png new file mode 100644 index 000000000..9148690e9 Binary files /dev/null and b/docs/images/icon-stop.png differ diff --git a/docs/images/scaffold_tree.png b/docs/images/scaffold_tree.png new file mode 100644 index 000000000..691ef6660 Binary files /dev/null and b/docs/images/scaffold_tree.png differ diff --git a/docs/locales/pt/LC_MESSAGES/chapter-07.mo b/docs/locales/pt/LC_MESSAGES/chapter-07.mo index 6c2591842..95c31493e 100644 Binary files a/docs/locales/pt/LC_MESSAGES/chapter-07.mo and b/docs/locales/pt/LC_MESSAGES/chapter-07.mo differ diff --git a/docs/locales/pt/LC_MESSAGES/chapter-07.po b/docs/locales/pt/LC_MESSAGES/chapter-07.po index 7283c08a9..2af2b2d47 100644 --- a/docs/locales/pt/LC_MESSAGES/chapter-07.po +++ b/docs/locales/pt/LC_MESSAGES/chapter-07.po @@ -3558,11 +3558,11 @@ msgstr "cache, em cache" #: ../../chapter-07.rst:2216 msgid "" "An example use which gives much faster selects is: ``rows = db(query)." -"select(cache=(cache.ram, 3600), cacheable=True)``:python Look at *Caching " +"select(cache=(cache.get, 3600), cacheable=True)``:python Look at *Caching " "selects* section in this chapter, to understand what the trade-offs are." msgstr "" "Um uso exemplo que dá muito mais rápido seleciona é: `` linhas = db " -"(query) .Select (cache = (cache.ram, 3600), cacheable = True) ``: Olhar " +"(query) .Select (cache = (cache.get, 3600), cacheable = True) ``: Olhar " "python na seção * * Caching seleciona neste capítulo, para entender o que " "os trade-offs são." @@ -5315,12 +5315,12 @@ msgstr "Selects com cache" msgid "" "The select method also takes a ``cache`` argument, which defaults to None. " "For caching purposes, it should be set to a tuple where the first element " -"is the cache model (``cache.ram``, ``cache.disk``, etc.), and the second " +"is the cache model (``cache.get`` when using py4web), and the second " "element is the expiration time in seconds." msgstr "" "O método de seleção também leva um argumento cache`` ``, cujo padrão é " "None. Para fins de armazenamento em cache, deve ser definido como um tuplo " -"em que o primeiro elemento é o modelo do cache ( `` cache.ram``, `` cache." +"em que o primeiro elemento é o modelo do cache ( `` cache.get``, `` cache." "disk``, etc), e o segundo elemento é o tempo de validade em segundo ." #: ../../chapter-07.rst:4046 diff --git a/docs/locales/pt/LC_MESSAGES/chapter-08.mo b/docs/locales/pt/LC_MESSAGES/chapter-08.mo index 7a2c279eb..8c8999538 100644 Binary files a/docs/locales/pt/LC_MESSAGES/chapter-08.mo and b/docs/locales/pt/LC_MESSAGES/chapter-08.mo differ diff --git a/docs/locales/pt/LC_MESSAGES/chapter-08.po b/docs/locales/pt/LC_MESSAGES/chapter-08.po index 10dfa7993..69f187df3 100644 --- a/docs/locales/pt/LC_MESSAGES/chapter-08.po +++ b/docs/locales/pt/LC_MESSAGES/chapter-08.po @@ -86,15 +86,15 @@ msgstr "A acção acima referida é exposto como:" msgid "" "**About request.POST**: keep in mind that **request.POST** only contains " "the form data that is posted using a **regular HTML-form** or javascript " -"**FormData** object. If you post just plain object (e.g. ``axios." -"post( 'path/to/api', {field:'some'} )``) you should pass **request.json** " +"**FormData** object. If you post just plain object (e.g.  +"Q.post( 'path/to/api', {field:'some'} )``) you should pass **request.json** " "instead of request.POST, since latter will contain just raw request-body " "which is string, not json. See bottle.py documentation for more details." msgstr "" "** Sobre request.POST **: Manter em mente que ** ** request.POST contém " "apenas os dados do formulário que é publicado utilizando um formulário HTML " "normal ** ** ou javascript ** FormData ** objeto. Se você postar apenas " -"objeto simples (por exemplo, `` axios.post ( 'path / to / api', {campo: " +"objeto simples (por exemplo, ``Q.post ( 'path / to / api', {campo: " "'alguns'}) ``) você deve passar request.json ** ** em vez de request.POST, " "desde Este último irá conter request-corpo apenas cru que é corda, não " "json. Consulte a documentação bottle.py para mais detalhes." diff --git a/docs/requirements.txt b/docs/requirements.txt index 0d556b606..6bcf77e38 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -5,5 +5,5 @@ PyStemmer<2.2 sphinx sphinx-tabs sphinx_rtd_theme -rinohtype +rinohtype==0.5.4 sphinxcontrib-spelling diff --git a/docs/spelling_wordlist_en.txt b/docs/spelling_wordlist_en.txt index ec244885e..558678ed1 100644 --- a/docs/spelling_wordlist_en.txt +++ b/docs/spelling_wordlist_en.txt @@ -5,6 +5,7 @@ appname args async Asyncio +attrs auth Auth autocomplete @@ -55,6 +56,7 @@ Dockerfile doesn dropdown dropdowns +enqueueing epub exe executesql @@ -71,6 +73,7 @@ firebird FireBird Firebird FireBirdEmbedded +formatter formstyle fullname gae @@ -94,6 +97,7 @@ https hx ibm ie +iframe incrementing informix Informix @@ -127,6 +131,7 @@ lib libs limitby localhost +longhash lookups mailto Makefile @@ -203,6 +208,7 @@ pysqlite pytds pyweb querystring +randint readonly redis reimplementation @@ -223,6 +229,8 @@ sql sqlite sso stateful +stderr +stdout stylesheet subclassing subfolder @@ -245,6 +253,7 @@ Teradata teredo textarea tracebacks +Twilio txt ual un diff --git a/docs/updateDocs.sh b/docs/updateDocs.sh index eabdc47f0..ab600fce1 100755 --- a/docs/updateDocs.sh +++ b/docs/updateDocs.sh @@ -76,10 +76,10 @@ for current_language in ${languages}; do export current_language # make spelling if english - if [ ${current_language} = 'en' ] - then - sphinx-build -b spelling docs/ docs/_build/spelling - fi + #if [ ${current_language} = 'en' ] + #then + #sphinx-build -b spelling docs/ docs/_build/spelling + #fi # make HTML # NOTE: this affect files in docs/locales/${current_language}/LC_MESSAGES/ sphinx-build -b html -D language="${current_language}" docs/ docs/_build/html/${current_language} diff --git a/py4web/__init__.py b/py4web/__init__.py index cd76a6fc2..52e2b5c41 100644 --- a/py4web/__init__.py +++ b/py4web/__init__.py @@ -2,7 +2,7 @@ __author__ = "Massimo Di Pierro " __license__ = "BSD-3-Clause" -__version__ = "1.20230718.1" +__version__ = "1.20241019.1" def _maybe_gevent(): @@ -23,15 +23,5 @@ def _maybe_gevent(): from .core import Translator # from pluralize from .core import action # main py4web decorator from .core import render # yatl -from .core import ( - DAL, - Cache, - Condition, - Flash, - Session, - abort, - check_compatible, - redirect, - request, - response, -) +from .core import (DAL, Cache, Condition, Flash, Session, abort, + check_compatible, redirect, request, response, safely) diff --git a/py4web/core.py b/py4web/core.py index 96526298c..da825f297 100644 --- a/py4web/core.py +++ b/py4web/core.py @@ -1,16 +1,18 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# pylint: disable=too-many-lines,line-too-long,too-many-branches,use-dict-literal,too-many-arguments,too-few-public-methods,too-many-locals,broad-exception-caught,cell-var-from-loop + """PY4WEB - a web framework for rapid development of efficient database driven web applications""" # Standard modules import asyncio -import cgitb import code import collections import copy import datetime import enum import functools +import html as sanitize_html import http.client import http.cookies import importlib.machinery @@ -18,7 +20,6 @@ import inspect import io import json -import linecache import logging import numbers import os @@ -35,13 +36,11 @@ import uuid import zipfile from collections import OrderedDict -from contextlib import redirect_stdout, redirect_stderr +from contextlib import redirect_stderr, redirect_stdout import portalocker from watchgod import awatch -from . import server_adapters - # Optional web servers for speed try: import gunicorn @@ -54,12 +53,14 @@ import ombott as bottle import pluralize import pydal +import pydal.validators import renoir import renoir.constants import renoir.writers import threadsafevariable import yatl +from . import server_adapters from .utils.misc import secure_dumps, secure_loads bottle.DefaultConfig.max_memfile_size = 16 * 1024 * 1024 @@ -86,6 +87,7 @@ "required_folder", "wsgi", "Condition", + "safely", ] PY4WEB_CMD = sys.argv[0] @@ -101,15 +103,11 @@ HELPERS = {name: getattr(yatl.helpers, name) for name in yatl.helpers.__all__} ART = r""" - /####### /## /##/## /## /## /## /######## /####### -| ##__ ##| ## /##/ ## | ##| ## /# | ##| ##_____/| ##__ ## -| ## \ ## \ ## /##/| ## | ##| ## /###| ##| ## | ## \ ## -| #######/ \ ####/ | ########| ##/## ## ##| ##### | ####### -| ##____/ \ ##/ |_____ ##| ####_ ####| ##__/ | ##__ ## -| ## | ## | ##| ###/ \ ###| ## | ## \ ## -| ## | ## | ##| ##/ \ ##| ########| #######/ -|__/ |__/ |__/|__/ \__/|________/|_______/ -Is still experimental... +██████◣◥█◣ ◢█◤ ██ ██ ██ ██ ███████ ██████◣ +██ ██ ◥█◣◢█◤ ██ ██ ██ ██ ██ ██ ██ +██████◤ ◥██◤ ███████ ██ ◢◣ ██ ██████ ██████ +██ ██ ██ ◥█◣◢██◣◢█◤ ██ ██ ██ +██ ██ ██ ◥██◤◥██◤ ███████ ██████◤ """ Field = pydal.Field @@ -125,17 +123,23 @@ # hold all framework hooks in one place # NOTE: `after_request` hooks are not currently used -_REQUEST_HOOKS = types.SimpleNamespace(before=set()) +_REQUEST_HOOKS = types.SimpleNamespace(before=[]) def _before_request(*args, **kw): - [h(*args, **kw) for h in _REQUEST_HOOKS.before] + [ # pylint: disable=expression-not-assigned + h(*args, **kw) for h in _REQUEST_HOOKS.before + ] bottle.default_app().add_hook("before_request", _before_request) +# set to true to debug issues with fixtures +DEBUG = False + def module2filename(module): + """given a module name as a string, convert to filename""" filename = os.path.join(*module.split(".")[1:]) filename = ( os.path.join(filename, "__init__.py") @@ -145,12 +149,21 @@ def module2filename(module): return filename +def load_module(name, path): + """load a module with name from math""" + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + def required_folder(*parts): """joins the args and creates the folder if not exists""" path = os.path.join(*parts) if not os.path.exists(path): os.makedirs(path) - assert os.path.isdir(path), "%s is not a folder as required" % path + assert os.path.isdir(path), f"{path} is not a folder as required" return path @@ -163,7 +176,7 @@ def safely(func, exceptions=(Exception,), log=False, default=None): return func() except exceptions as err: if log: - logging.warn(str(err)) + logging.warning(str(err)) return default() if callable(default) else default @@ -173,7 +186,12 @@ def safely(func, exceptions=(Exception,), log=False, default=None): class Node: - def __init__(self, key=None, value=None, t=None, m=None, prev=None, next=None): + """A node for the LRU cache""" + + def __init__( + self, key=None, value=None, t=None, m=None, prev=None, next=None + ): # pylint: disable=redefined-builtin + """create a node of the LRU cache""" self.key, self.value, self.t, self.m, self.prev, self.next = ( key, value, @@ -198,6 +216,7 @@ class Cache: """ def __init__(self, size=1000): + """Create an LRU caching object""" self.free = size self.head = Node() self.tail = Node() @@ -208,7 +227,12 @@ def __init__(self, size=1000): def get(self, key, callback, expiration=3600, monitor=None): """If key not stored or key has expired and monitor == None or monitor() value has changed, returns value = callback()""" - node, t0 = self.mapping.get(key), time.time() + # set defaults + node = self.mapping.get(key) + t0 = time.time() + value = None + t = t0 + # update with self.lock: if node: # if a node was found remove it from storage @@ -229,7 +253,8 @@ def get(self, key, callback, expiration=3600, monitor=None): # ignore the value found node = None if node is None: - value, t = callback(), t0 + value = callback() + t = t0 # add the new node back into storage with self.lock: new_node = Node(key, value, t, m, prev=self.head, next=self.head.next) @@ -242,10 +267,12 @@ def get(self, key, callback, expiration=3600, monitor=None): return value def memoize(self, expiration=3600): + """Decorator to memorize the output of any fuction""" + def decorator(func): @functools.wraps(func) def memoized_func(*args, **kwargs): - key = "%s:%s:%s:%s" % (func.__module__, func.__name__, args, kwargs) + key = f"{func.__module__}:{func.__name__}:{args}:{kwargs}" return self.get( key, lambda args=args, kwargs=kwargs: func(*args, **kwargs), @@ -262,31 +289,31 @@ def memoized_func(*args, **kwargs): ######################################################################################### -def objectify(obj): +def objectify(obj): # pylint: disable=too-many-return-statements """converts the obj(ect) into a json serializable object""" if isinstance(obj, numbers.Integral): return int(obj) - elif isinstance(obj, (numbers.Rational, numbers.Real)): + if isinstance(obj, (numbers.Rational, numbers.Real)): return float(obj) - elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)): + if isinstance(obj, (datetime.date, datetime.datetime, datetime.time)): return obj.isoformat().replace("T", " ") - elif isinstance(obj, str): + if isinstance(obj, str): return obj - elif isinstance(obj, dict): + if isinstance(obj, dict): return obj - elif hasattr(obj, "as_list"): + if hasattr(obj, "as_list"): return obj.as_list() - elif hasattr(obj, "as_dict"): + if hasattr(obj, "as_dict"): return obj.as_dict() - elif hasattr(obj, "__iter__") or isinstance(obj, types.GeneratorType): + if hasattr(obj, "__iter__") or isinstance(obj, types.GeneratorType): return list(obj) - elif hasattr(obj, "xml"): + if hasattr(obj, "xml"): return obj.xml() - elif isinstance( + if isinstance( obj, enum.Enum ): # Enum class handled specially to address self reference in __dict__ return dict(name=obj.name, value=obj.value, __class__=obj.__class__.__name__) - elif hasattr(obj, "__dict__") and hasattr(obj, "__class__"): + if hasattr(obj, "__dict__") and hasattr(obj, "__class__"): d = dict(obj.__dict__) d["__class__"] = obj.__class__.__name__ return d @@ -294,6 +321,7 @@ def objectify(obj): def dumps(obj, sort_keys=True, indent=2): + """General purpose memoize function with sane default""" return json.dumps(obj, default=objectify, sort_keys=sort_keys, indent=indent) @@ -302,85 +330,107 @@ def dumps(obj, sort_keys=True, indent=2): ######################################################################################### -class Fixture: +class LocalUndefined(RuntimeError): + """ + Exception raised trying to access an unitialized thread local + from a Fixture + """ + - __request_master_ctx__ = threading.local() - __fixture_debug__ = False +class BareFixture: + """Minimal Fixture class - without thread local logic""" # normally on_success/on_error are only called if none of the previous # on_request failed, if a fixture is_hook then on_error is always called. is_hook = False - @classmethod - def __init_request_ctx__(cls): - cls.__request_master_ctx__.request_ctx = dict() + def on_request(self, context): # pylint: disable=unused-argument + """Method that will be called when a new HTTP request arrives""" - @classmethod - def __mount_local__(cls, self, storage): - cls.__request_master_ctx__.request_ctx[self] = storage + def on_error(self, context): # pylint: disable=unused-argument + """Method that will be called when an HTTP request errors""" - @property - def _safe_local(self): - try: - ret = self.__request_master_ctx__.request_ctx[self] - except (KeyError, AttributeError) as err: - msg = "py4web hint: check @action.uses() for the missing fixture {}".format( - self - ) - raise RuntimeError(msg) from err - return ret + def on_success(self, context): # pylint: disable=unused-argument + """Method that will be called when an HTTP request succeeds""" - @_safe_local.setter - def _safe_local(self, storage): - self.__mount_local__(self, storage) - def is_valid(self): - """check if the fixture is valid in context""" - ctx = self.__request_master_ctx__.request_ctx - if self not in ctx: - logging.warn( - "attempted access to fixture %s from outside a request", - self.__class__.__name__, - ) - return False - return True +class Fixture(BareFixture): + """ + Fixture class - with thread local logic + all fixtures should inherit from this class + """ - def on_request(self, context): - pass # called when a request arrives + ### being logic to handle safe thread local - def on_error(self, context): - pass # called when a request errors + _local = threading.local() - def on_success(self, context): - pass # called when a request is successful + @staticmethod + def local_initialize(obj): + """To be called in on_request if the Fixtures needs a thread local dict""" + if not hasattr(Fixture._local, "request_ctx"): + Fixture._local.request_ctx = {} + if obj in Fixture._local.request_ctx: + raise RuntimeError(f"initialize_thread_local called twice for {obj}") + Fixture._local.request_ctx[obj] = types.SimpleNamespace() + + @staticmethod + def local_delete(obj): + """To be called in on_success and on_error to cleat the thread local""" + del Fixture._local.request_ctx[obj] + + @property + def local(self): + """Returns the fixture thread local dict if initialized else a default one""" + try: + return Fixture._local.request_ctx[self] + except (AttributeError, KeyError): + raise LocalUndefined(f"thread local not initialized for {self}") from None + + def is_valid(self): + """Checks if the fixture has a valid thread local dict""" + try: + Fixture._local.request_ctx[self] # pylint: disable=pointless-statement + return True + except (AttributeError, KeyError): + return False + ### end logic to handle safe thread local -_REQUEST_HOOKS.before.add(Fixture.__init_request_ctx__) +class Translator(BareFixture, pluralize.Translator): + """a Fixture wrapper for the pluralize.Translator""" -class Translator(pluralize.Translator, Fixture): def on_request(self, context): + """Sets the request language from the request header""" + # important: pluralize.Translator has its own thread local self.select(request.headers.get("Accept-Language", "en")) def on_success(self, context): - response.headers["Content-Language"] = self.local.tag + """Inject the selected language in the response header""" + response.headers.setdefault("Content-Language", self.local.tag) class DAL(pydal.DAL, Fixture): + """a Fixture wrappre for pydal.DAL""" + def on_request(self, context): + """Retrieves a database connection from the pool""" + # important: the connection pool handles its own thread local self.get_connection_from_pool_or_new() threadsafevariable.ThreadSafeVariable.restore(ICECUBE) def on_error(self, context): + """Rollback and recycle connection""" self.recycle_connection_in_pool_or_close("rollback") def on_success(self, context): + """Commit and recycle connection""" self.recycle_connection_in_pool_or_close("commit") # make sure some variables in pydal are thread safe def thread_safe_pydal_patch(): - Field = pydal.DAL.Field + """Make the selected fields attributes thread local variables""" tsafe_attrs = [ "readable", "writable", @@ -442,24 +492,18 @@ def index(): Also notice all Flash objects share the same threading local so act as singletons """ - # this essential makes flash a singleton - # necessary because auth defines its own flash - # possible because flash does not depend on the app - - @property - def local(self): - return self._safe_local - def on_request(self, context): - self._safe_local = types.SimpleNamespace() + """Retrieves flash message from cookie if present""" + Fixture.local_initialize(self) # when a new request arrives we look for a flash message in the cookie flash = request.get_cookie("py4web-flash") if flash: - self.local.flash = json.loads(flash) + self.local.flash = safely(lambda: json.loads(flash), default=None) else: self.local.flash = None def on_success(self, context): + """Stores the flash message in cookie""" # if we redirect and have a flash message we move it to the session status = context["status"] if status == 303 and self.local.flash: @@ -474,17 +518,11 @@ def on_success(self, context): context["template_inject"]["flash"] = flash else: context["template_inject"] = dict(flash=flash) - else: - if self.local.flash is not None: - response.headers["component-flash"] = json.dumps(flash) - self.local.flash = None - self.local.__dict__.clear() - - def on_error(self, context): - """Clears the local to prevent leakage.""" - self.local.__dict__.clear() + elif self.local.flash is not None: + response.headers.setdefault("component-flash", json.dumps(flash)) def set(self, message, _class="", sanitize=True): + """Stores a message in the object thread safe storage""" # we set a flash message if sanitize: message = yatl.sanitizer.xmlescape(message) @@ -497,19 +535,22 @@ def set(self, message, _class="", sanitize=True): class RenoirXMLEscapeMixin: + """for internal Renoir use""" + def _escape_data(self, data): """Allows Renoir to convert yatl helpers to strings""" return safely( - lambda: data.xml(), default=lambda: self._to_html(self._to_unicode(data)) + lambda: data.xml(), # pylint: disable=unnecessary-lambda + default=lambda: self._to_html(self._to_unicode(data)), ) class RenoirCustomWriter(RenoirXMLEscapeMixin, renoir.writers.Writer): - ... + """for internal Renoir use""" class RenoirCustomEscapeAllWriter(RenoirXMLEscapeMixin, renoir.writers.EscapeAllWriter): - ... + """for internal Renoir use""" class Renoir(renoir.Renoir): @@ -525,36 +566,47 @@ def render( content=None, filename=None, path=".", - context={}, + context=None, delimiters="[[ ]]", cached_renoir_engines=Cache(100), ): """ - renders the template using renoire, same API as yatl.render, does caching of + Renders the template using renoire, same API as yatl.render, does caching of both Renoire engine and source files """ + context = context or {} engine = cached_renoir_engines.get( (path, delimiters), lambda: Renoir(path=path, delimiters=delimiters.split(" "), reload=True), ) if content is not None: - return engine._render(content, context=context) + return engine._render( # pylint: disable=protected-access + content, context=context + ) return engine.render(filename, context=context) class Template(Fixture): + """The Template Fixture class""" cache = Cache(100) def __init__(self, filename, path=None, delimiters="[[ ]]"): + """Initialized the template object""" self.filename = filename self.path = path self.delimiters = delimiters def on_success(self, context): + """ + Filters the context output through the template + Also injects helpers in the output dict + """ output = context["output"] + + # we only proceed furthed if the output is a dict if not isinstance(output, dict): - return output + return ctx = dict(request=request) ctx.update(HELPERS) @@ -580,16 +632,18 @@ def on_success(self, context): class Session(Fixture): + """The Session Fixture""" # All apps share the same default secret if not specified. # important for _dashboard reload # the actual value is loaded from a file SECRET = None - __slots__ = ["_safe", "secret", "expiration", "algorithm", "storage", "same_site"] + _params = {} @property - def local(self): - return self._safe_local + def params(self): + """Returns the object parameters""" + return Session._params[self] def __init__( self, @@ -601,41 +655,56 @@ def __init__( name="{app_name}_session", ): """ + Creates a session object. + secret is the shared key used to encrypt the session (using algorithm) expiration is in seconds (optional) storage must have a get(key) and set(key,value,expiration) methods session is stored signed and encrypted in the cookie """ - # assert Session.SECRET, "Missing Session.SECRET" - self.secret = secret or Session.SECRET - self.expiration = expiration - self.algorithm = algorithm - self.storage = storage - self.same_site = same_site - self.name = name + secret = secret or Session.SECRET + assert ( + isinstance(secret, str) + and not pydal.validators.IS_STRONG(entropy=50)(secret)[1] + ), "Not a good secret" + if isinstance(storage, Session): - self.__prerequisites__ = [storage] - if hasattr(storage, "__prerequisites__"): - self.__prerequisites__ = storage.__prerequisites__ - - def initialize(self, app_name="unknown", data=None, changed=False, secure=False): - self._safe_local = types.SimpleNamespace() - local = self.local - local.changed = changed - local.data = data or {} - local.session_cookie_name = self.name.format(app_name=app_name) - local.secure = secure + prerequisites = [storage] + elif hasattr(storage, "__prerequisites__"): + prerequisites = storage.__prerequisites__ + else: + prerequisites = [] + Session._params[self] = type( + "Object", + (), + dict( + secret=secret, + expiration=expiration, + algorithm=algorithm, + storage=storage, + same_site=same_site, + name=name, + prerequisites=prerequisites, + ), + ) + + @property + def __prerequisites__(self): + """Returns the session prerequisite fixtures""" + return self.params.prerequisites def load(self): - self.initialize( - app_name=request.app_name, - changed=False, - secure=request.url.startswith("https"), - ) + """Loads a session""" + app_name = request.app_name + params = self.params self_local = self.local - raw_token = request.get_cookie( - self_local.session_cookie_name - ) or request.query.get("_session_token") + self_local.changed = False + self_local.data = {} + self_local.cookie_name = params.name.format(app_name=app_name) + self_local.secure = request.url.startswith("https") + raw_token = request.get_cookie(self_local.cookie_name) or request.query.get( + "_session_token" + ) if not raw_token and request.method in {"POST", "PUT", "DELETE", "PATCH"}: raw_token = ( request.forms @@ -643,99 +712,126 @@ def load(self): or request.json and request.json.get("_session_token") ) - if Fixture.__fixture_debug__: + if DEBUG: logging.debug("Session token found %s", raw_token) + data = {} + # if we have a token in the query string of cookie if raw_token: try: - if self.storage: + # if session i stored serverside + if params.storage: + # used token as id and retrieve data token_data = raw_token.encode() - json_data = self.storage.get(token_data) + json_data = params.storage.get(token_data) + if isinstance(json_data, bytes): + json_data = json_data.decode("utf8") if json_data: - self_local.data = json.loads(json_data) + data = json.loads(json_data) else: + # rertieve the data from inside the token itself try: - self_local.data = secure_loads(raw_token, self.secret.encode()) + data = secure_loads(raw_token, params.secret.encode()) except (AssertionError, json.JSONDecodeError): - self_local.data = {} - if self.expiration is not None and self.storage is None: - assert self_local.data["timestamp"] > time.time() - int( - self.expiration - ) - assert self.get_data().get("secure") == self_local.secure + data = {} except Exception as err: - if Fixture.__fixture_debug__: + # something went wrong, unable to load session data + if DEBUG: logging.debug("Session error %s", err) + # if the session data is valid update the current session if ( - self.get_data().get("session_cookie_name") != self_local.session_cookie_name - or "uuid" not in self.get_data() + data.get("cookie_name") == self_local.cookie_name # have valid cookie + and data.get("secure") == self_local.secure # have valid security + and data.get("uuid") is not None # have a uuid + and ( + params.expiration is None # has not expired + or data["timestamp"] > time.time() - int(params.expiration) + ) ): - self.clear() - - def get_data(self): - return getattr(self.local, "data", {}) + self_local.data.update(data) # the take the loaded data def save(self): + """Saves the session""" + params = self.params self_local = self.local + # make sure the session constain these basic veriables + if "uuid" not in self_local.data: + self_local.data["uuid"] = str(uuid.uuid4()) self_local.data["timestamp"] = time.time() - self_local.data["session_cookie_name"] = self_local.session_cookie_name - if self.storage: + self_local.data["secure"] = self_local.secure + self_local.data["cookie_name"] = self_local.cookie_name + if params.storage: cookie_data = self_local.data["uuid"] - self.storage.set(cookie_data, json.dumps(self_local.data), self.expiration) + params.storage.set( + cookie_data, json.dumps(self_local.data), params.expiration + ) else: - cookie_data = secure_dumps(self_local.data, self.secret.encode()) - if Fixture.__fixture_debug__: + cookie_data = secure_dumps(self_local.data, params.secret.encode()) + if DEBUG: logging.debug("Session stored %s", cookie_data) response.set_cookie( - self_local.session_cookie_name, + self_local.cookie_name, cookie_data, path="/", secure=self_local.secure, - same_site=self.same_site, + same_site=params.same_site, + httponly=True, ) def get(self, key, default=None): - return self.get_data().get(key, default) + """Get the value for the key from session""" + try: + return self.local.data.get(key, default) + except LocalUndefined: + return default def __getitem__(self, key): - return self.get_data()[key] + """Session key getter""" + return self.local.data[key] def __delitem__(self, key): - if key in self.get_data(): + """Deletes a key from the session""" + if key in self.local.data: self.local.changed = True del self.local.data[key] def __setitem__(self, key, value): + """Session key setter""" self.local.changed = True self.local.data[key] = value def __contains__(self, other): - return other in self.get_data() + """Checks if a key in session""" + return other in self.local.data def keys(self): - return self.get_data().keys() + """Returns the session keys""" + return self.local.data.keys() def items(self): - return self.get_data().items() + """Returns the (key, value) in session""" + return self.local.data.items() def __iter__(self): - yield from self.get_data().keys() + """Iterates over the session keys""" + yield from self.local.data.keys() + + __getattr__ = get + __setattr__ = __setitem__ + __delattr__ = __delitem__ def clear(self): - """Produces a brand-new session.""" + """Clears the session key""" self_local = self.local self_local.changed = True self_local.data.clear() - self_local.data["uuid"] = str(uuid.uuid1()) - self_local.data["secure"] = self_local.secure def on_request(self, context): + """Initializes the session thread local and tries to load a session""" + Fixture.local_initialize(self) self.load() - def on_error(self, context): - if self.local.changed: - self.save() - def on_success(self, context): + """Saves the session if its content changed""" if self.local.changed: self.save() @@ -745,16 +841,18 @@ def on_success(self, context): ######################################################################################### -def URL( +def URL( # pylint: disable=invalid-name *parts, - vars=None, - hash=None, + vars=None, # pylint: disable=redefined-builtin + hash=None, # pylint: disable=redefined-builtin scheme=False, signer=None, use_appname=None, static_version=None, ): """ + Generates a URL for the action. + Examples: URL('a','b',vars=dict(x=1),hash='y') -> /{script_name?}/{app_name?}/a/b?x=1#y URL('a','b',vars=dict(x=1),scheme=None) -> //{domain}/{script_name?}/{app_name?}/a/b?x=1 @@ -765,10 +863,11 @@ def URL( if use_appname is None: # force use_appname on domain-unmapped apps use_appname = not request.environ.get("HTTP_X_PY4WEB_APPNAME") + has_appname = False if use_appname: # app_name is not set by py4web shell app_name = getattr(request, "app_name", None) - has_appname = use_appname and app_name + has_appname = use_appname and app_name script_name = ( request.environ.get("SCRIPT_NAME", "") or request.environ.get("HTTP_X_SCRIPT_NAME", "") @@ -776,16 +875,16 @@ def URL( if parts and parts[0].startswith("/"): prefix = "" elif has_appname and app_name != "_default": - prefix = "%s/%s/" % (script_name, app_name) + prefix = f"{script_name}/{app_name}/" else: - prefix = "%s/" % script_name + prefix = f"{script_name}/" broken_parts = [] for part in parts: broken_parts += str(part).rstrip("/").split("/") if static_version != "" and broken_parts and broken_parts[0] == "static": if not static_version: # try to retrieve from __init__.py - app_module = "apps.%s" % app_name if has_appname else "apps" + app_module = f"apps.{app_name}" if has_appname else "apps" try: static_version = getattr( sys.modules[app_module], "__static_version__", None @@ -805,10 +904,10 @@ def URL( signer.sign(prefix + "/".join(broken_parts), urlvars) if urlvars: url += "?" + "&".join( - "%s=%s" % (k, urllib.parse.quote(str(v))) for k, v in urlvars.items() + f"{k}={urllib.parse.quote(str(v))}" for k, v in urlvars.items() ) if hash: - url += "#%s" % hash + url += f"#{hash}" if scheme is not False: original_url = request.environ.get("HTTP_ORIGIN") or request.url orig_scheme, _, domain = original_url.split("/", 3)[:3] @@ -818,7 +917,7 @@ def URL( scheme = "" else: scheme += ":" - url = "%s//%s%s" % (scheme, domain, url) + url = f"{scheme}//{domain}{url}" return url @@ -828,34 +927,35 @@ def URL( class HTTP(BaseException): - """An exception that is considered success""" - def __init__(self, status, body="", headers={}): + def __init__(self, status, body="", headers=None): + """Makes an HTTP object""" self.status = status self.body = body - self.headers = headers + self.headers = headers or {} def redirect(location): - """raises HTTP(303) to the specified location""" - response.headers["Location"] = location + """Raises HTTP(303) to redirect to the specified location""" + response.headers.setdefault("Location", location) raise HTTP(303) -class action: +class action: # pylint: disable=invalid-name """@action(...) is a decorator for functions to be exposed as actions""" registered = set() app_name = "_default" def __init__(self, path, **kwargs): + """Constructs the action decorator""" self.path = path self.kwargs = kwargs @staticmethod def uses(*fixtures_in): - """Find all fixtures, including dependencies, topologically sorted""" + """Used to declare needed fixtures, they will be topologically sorted""" fixtures = [] reversed_fixtures = [] stack = list(fixtures_in) @@ -870,18 +970,17 @@ def uses(*fixtures_in): fixtures.append(fixture) def decorator(func): - - if Fixture.__fixture_debug__: + if DEBUG: # in debug mode log all calls to fixtures - def call(f, context): + def call_f(f, context): logging.debug( - f"Calling {f.__self__.__class__.__name__}.{f.__name__}" + "Calling %s.%s", f.__self__.__class__.__name__, f.__name__ ) return f(context) else: - def call(f, context): + def call_f(f, context): return f(context) @functools.wraps(func) @@ -897,12 +996,12 @@ def wrapper(*args, **kwargs): } try: for fixture in fixtures: - call(fixture.on_request, context) + call_f(fixture.on_request, context) processed.append(fixture) context["output"] = func(*args, **kwargs) - except HTTP as http: - context["status"] = http.status - raise http + except HTTP as http_exception: + context["status"] = http_exception.status + raise http_exception except bottle.HTTPError as error: context["exception"] = error except bottle.HTTPResponse: @@ -914,13 +1013,16 @@ def wrapper(*args, **kwargs): if fixture in processed or getattr(fixture, "is_hook", False): try: if context.get("exception"): - call(fixture.on_error, context) + call_f(fixture.on_error, context) else: - call(fixture.on_success, context) - except Exception as error: - context["exception"] = context.get("exception") or error - if context.get("exception"): - raise context["exception"] + call_f(fixture.on_success, context) + except Exception as err: + context["exception"] = context.get("exception") or err + for fixture in reversed(fixtures): + safely(lambda: Fixture.local_delete(fixture)) + exception = context.get("exception") + if isinstance(exception, Exception): + raise exception return context.get("output", "") return wrapper @@ -937,16 +1039,23 @@ def wrapper(*func_args, **func_kwargs): request.app_name = app_name ret = func(*func_args, **func_kwargs) if isinstance(ret, (list, dict)): - response.headers["Content-Type"] = "application/json" + response.headers.setdefault("Content-Type", "application/json") ret = dumps(ret) + elif ret is None: + ret = "" + elif isinstance(ret, yatl.helpers.TAGGER): + ret = str(ret) + elif not hasattr(ret, "__iter__"): + raise RuntimeError(f"Cannot return type {ret.__class__.__name__}") return ret - except HTTP as http: - response.status = http.status - response.headers.update(http.headers) - return http.body + except HTTP as http_exception: + response.status = http_exception.status + response.headers.update(http_exception.headers) + body = http_exception.body + return dumps(body) if isinstance(body, (list, dict)) else str(body) except bottle.HTTPResponse: raise - except Exception: + except Exception: # pylint: disable=broad-exception-caught snapshot = get_error_snapshot() logging.error(snapshot["traceback"]) ticket_uuid = error_logger.log(request.app_name, snapshot) or "unknown" @@ -975,12 +1084,16 @@ def __call__(self, func): class Condition(Fixture): + """The Condition Fixture""" + def __init__(self, condition, on_false=None, exception=HTTP(400)): + """Creates a fixture that checks for a given condition""" self.condition = condition self.on_false = on_false self.exception = exception def on_request(self, context): + """Checks if the condition is true or false""" if not self.condition(): if self.on_false is not None: self.on_false() @@ -991,7 +1104,9 @@ def on_request(self, context): # Monkey Patch: Cookies ######################################################################################### -http.cookies.Morsel._reserved["same-site"] = "SameSite" +http.cookies.Morsel._reserved[ # pylint: disable=protected-access + "same-site" +] = "SameSite" ######################################################################################### # Monkey Patch: ssl bug for gevent @@ -1011,6 +1126,7 @@ def new_sslwrap( ca_certs=None, ciphers=None, ): + """Used to support HTTP""" context = __ssl__.SSLContext(ssl_version) context.verify_mode = cert_reqs or __ssl__.CERT_NONE if ca_certs: @@ -1020,7 +1136,9 @@ def new_sslwrap( if ciphers: context.set_ciphers(ciphers) caller_self = inspect.currentframe().f_back.f_locals["self"] - return context._wrap_socket(sock, server_side=server_side, ssl_sock=caller_self) + return context._wrap_socket( # pylint: disable=protected-access + sock, server_side=server_side, ssl_sock=caller_self + ) ######################################################################################### @@ -1029,7 +1147,7 @@ def new_sslwrap( def get_error_snapshot(depth=5): - """Return a dict describing a given traceback (based on cgitb.text).""" + """Return a dict describing a given traceback.""" tb = traceback.format_exc() errorlog = os.environ.get("PY4WEB_ERRORLOG") @@ -1040,7 +1158,7 @@ def get_error_snapshot(depth=5): elif errorlog == ":stdout": sys.stdout.write(msg) elif errorlog == "tickets_only": - pass + ... else: with portalocker.Lock(errorlog, "a", timeout=2) as fp: fp.write(msg) @@ -1081,38 +1199,30 @@ def get_error_snapshot(depth=5): del etb # Prevent circular references that would cause memory leaks data["stackframes"] = stackframes = [] - for frame, file, lnum, func, lines, idx in items: + for frame, file, lnum, func, lines, idx in items: # pylint: disable=unused-variable file = file and os.path.abspath(file) or "?" - args, varargs, varkw, locals = inspect.getargvalues(frame) + # TODO: call inspect.getargvalues(frame) and get more info # Basic frame information f = {"file": file, "func": func, "lnum": lnum} f["code"] = lines - # FIXME: disable this for now until we understand why this goes into infinite loop - if False: - line_vars = cgitb.scanvars( - lambda: linecache.getline(file, lnum), frame, locals - ) - # Dump local variables (referenced in current line only) - f["vars"] = { - key: repr(value) - for key, value in locals.items() - if not key.startswith("__") - } stackframes.append(f) return data class SimpleErrorLogger: + """Simple Error Logger""" + def log(self, app_name, snapshot): - """logs the error""" - logging.error("%s error:\n%s" % (app_name, snapshot["traceback"])) - return None + """Logs the error""" + logging.error("%s error:\n%s", app_name, snapshot["traceback"]) class DatabaseErrorLogger: + """Database Error Logger""" + def __init__(self): - """creates the py4web_error table in the service database""" + """Creates the py4web_error table in the service database""" uri = os.environ["PY4WEB_SERVICE_DB_URI"] folder = os.environ["PY4WEB_SERVICE_FOLDER"] self.db = DAL(uri, folder=folder) @@ -1130,7 +1240,7 @@ def __init__(self): self.db.commit() def log(self, app_name, error_snapshot): - """store error snapshot (ticket) in the database""" + """Store error snapshot (ticket) in the database""" ticket_uuid = str(uuid.uuid4()) try: self.db.py4web_error.insert( @@ -1145,13 +1255,13 @@ def log(self, app_name, error_snapshot): ) self.db.commit() return ticket_uuid - except Exception as err: + except Exception as err: # pylint: disable=broad-exception-caught logging.error(str(err)) self.db.rollback() return None def get(self, ticket_uuid=None): - """retrieve a ticket from error database""" + """Retrieve a ticket from error database""" db = self.db if ticket_uuid: query, orderby = db.py4web_error.uuid == ticket_uuid, None @@ -1176,14 +1286,13 @@ def get(self, ticket_uuid=None): return rows if not ticket_uuid else rows[0] if rows else None def clear(self): - """erase all tickets from database""" + """Erase all tickets from database""" db = self.db db(db.py4web_error).delete() self.db.commit() class ErrorLogger: - """ To create your own custom logger for an app: @@ -1201,17 +1310,17 @@ def __init__(self): self.plugins = {} def initialize(self): - """try inizalize database if we have service folder""" + """Try inizalize database if we have service folder""" self.database_logger = safely(DatabaseErrorLogger, log=True) def _get_logger(self, app_name): - """get the appropriate logger for the app""" + """Get the appropriate logger for the app""" return ( self.plugins.get(app_name) or self.database_logger or self.fallback_logger ) def log(self, app_name, error_snapshot): - """log the error snapshot""" + """Log the error snapshot""" logger = self._get_logger(app_name) ticket_uuid = safely(lambda: logger.log(app_name, error_snapshot)) if not ticket_uuid: @@ -1226,15 +1335,10 @@ def log(self, app_name, error_snapshot): ######################################################################################### -class StreamProxy: - def __init__(self, stream): - self._stream = stream - - def write(self, *args, **kwargs): - return self._stream.write(*args, **kwargs) - - class Reloader: + """ + Class responsible for loading/readloading apps + """ ROUTES = collections.defaultdict(list) MODULES = {} @@ -1242,10 +1346,12 @@ class Reloader: @staticmethod def install_reloader_hook(): + """Installs the Reloader hook, checks for changes at every request""" + # used by watcher - def hook(*a, **k): + def hook(*args, **kwargs): # pylint: disable=unused-argument app_name = request.path.split("/")[1] - if not app_name in Reloader.ROUTES: + if app_name not in Reloader.ROUTES: app_name = "_default" if DIRTY_APPS.get(app_name): Reloader.import_app(app_name) @@ -1253,10 +1359,11 @@ def hook(*a, **k): ## APP_WATCH tasks, if used by any app try_app_watch_tasks() - _REQUEST_HOOKS.before.add(hook) + _REQUEST_HOOKS.before.append(hook) @staticmethod def clear_routes(app_names=None): + """Clears all stored routes""" remove_route = bottle.default_app().router.remove if app_names is None: app_names = Reloader.ROUTES.keys() @@ -1273,8 +1380,7 @@ def import_apps(): # if first time reload dummy top module if not Reloader.MODULES: path = os.path.join(folder, "__init__.py") - loader = importlib.machinery.SourceFileLoader("apps", path) - loader.load_module() + load_module("apps", path) # noqa: F841 # Then load all the apps as submodules if os.environ.get("PY4WEB_APP_NAMES"): app_names = os.environ.get("PY4WEB_APP_NAMES").split(",") @@ -1285,6 +1391,7 @@ def import_apps(): @staticmethod def import_app(app_name, clear_before_import=True): + """Imports a specified app and its routes""" if clear_before_import: Reloader.clear_routes([app_name]) Reloader.ROUTES[app_name] = [] @@ -1293,9 +1400,8 @@ def import_app(app_name, clear_before_import=True): init = os.path.join(path, "__init__.py") if os.path.isdir(path) and not path.endswith("__") and os.path.exists(init): - action.app_name = app_name - module_name = "apps.%s" % app_name + module_name = f"apps.{app_name}" def clear_modules(): # all files/submodules @@ -1310,42 +1416,27 @@ def clear_modules(): try: module = Reloader.MODULES.get(app_name) if not module: - click.echo("[ ] loading %s ..." % app_name) + click.echo(f"[ ] loading {app_name} ...") else: - click.echo("[ ] reloading %s ..." % app_name) + click.echo(f"[ ] reloading {app_name} ...") # forget the module del Reloader.MODULES[app_name] clear_modules() - load_module_message = None - buf_out = StreamProxy(io.StringIO()) - buf_err = StreamProxy(buf_out._stream) - with redirect_stdout(buf_out), redirect_stderr(buf_err): - module = importlib.machinery.SourceFileLoader( - module_name, init - ).load_module() - load_module_message = buf_out._stream.getvalue() - buf_out._stream.close() - buf_out._stream = sys.stdout - buf_err._stream = sys.stderr - - if load_module_message: - click.secho("\x1b[A output %s " % app_name, fg="yellow") - click.echo(load_module_message) - - click.secho("\x1b[A[X] loaded %s " % app_name, fg="green") + module = load_module(module_name, init) + + click.secho(f"\x1b[A[X] loaded {app_name} ", fg="green") Reloader.MODULES[app_name] = module Reloader.ERRORS[app_name] = None - except Exception as err: + except Exception as err: # pylint: disable=broad-exception-caught Reloader.ERRORS[app_name] = traceback.format_exc() error_logger.log(app_name, get_error_snapshot()) click.secho( - "\x1b[A[FAILED] loading %s (%s)" % (app_name, err), + f"\x1b[A[FAILED] loading {app_name} ({err})", fg="red", ) # clear all files/submodules if the loading fails clear_modules() - return None # Expose static files with support for static asset management static_folder = os.path.join(path, "static") @@ -1356,6 +1447,7 @@ def clear_modules(): path = prefix + r"/static/" def server_static(fp, static_folder=static_folder): + """Action that serves static/ files""" filename = fp response.headers.setdefault("Pragma", "cache") response.headers.setdefault("Cache-Control", "private") @@ -1363,19 +1455,24 @@ def server_static(fp, static_folder=static_folder): Reloader.register_route(app_name, path, {"method": "GET"}, server_static) + # Very important to make sure actions can modify Field attributes + # in a thread safe manner ICECUBE.update(threadsafevariable.ThreadSafeVariable.freeze()) @staticmethod def register_route(app_name, rule, kwargs, func): + """Given an app_name and a rule registers the corresponding routes""" url_prefix = os.environ.get("PY4WEB_URL_PREFIX", "") if url_prefix and rule == "/": rule = "" else: rule = url_prefix + rule dec_func = action.catch_errors(app_name, func) + if "method" not in kwargs: + kwargs["method"] = ["GET", "POST"] bottle.route(rule, **kwargs)(dec_func) filename = module2filename(func.__module__) - methods = kwargs.get("method", ["GET"]) + methods = kwargs.get("method") if isinstance(methods, str): methods = [methods] for method in methods: @@ -1404,27 +1501,32 @@ def register_route(app_name, rule, kwargs, func): } -def error_page(code, button_text=None, href="#", color=None, message=None): - message = http.client.responses[code].upper() if message is None else message +def error_page(http_code, button_text=None, href="#", color=None, message=None): + """Generates an error page""" + if button_text: + button_text = sanitize_html.escape(button_text) + href = sanitize_html.escape(href) + message = http.client.responses[http_code].upper() if message is None else message color = ( - {"4": "#F44336", "5": "#607D8B"}.get(str(code)[0], "#2196F3") + {"4": "#F44336", "5": "#607D8B"}.get(str(http_code)[0], "#2196F3") if not color else color ) context = dict( - code=code, message=message, button_text=button_text, href=href, color=color + code=http_code, message=message, button_text=button_text, href=href, color=color ) # if client accepts 'application/json' - return json if re.search(REGEX_APPJSON, request.headers.get("accept", "")): - response.status = code + response.status = http_code return json.dumps(context) # else - return html error-page - content = ERROR_PAGES.get(code) or ERROR_PAGES["*"] + content = ERROR_PAGES.get(http_code) or ERROR_PAGES["*"] return render(content=content, context=context, delimiters="[[ ]]") @bottle.error(404) -def error404(error): +def error404(error): # pylint: disable=unused-argument + """Generates a 404 page""" guess_app_name = ( "index" if request.environ.get("HTTP_X_PY4WEB_APPNAME") @@ -1451,24 +1553,16 @@ def error404(error): APP_WATCH = {"files": dict(), "handlers": OrderedDict(), "tasks": dict()} -""" Decorator that binds a func as an watchdog handler of non-'.py' files. -Paths to files must be relative to app, w/o app name(folder). - -@app_watch_handler(['static/sass/all.sass', 'static/sass/main.sass']) -def sass_compile(changed_files): - print(changed_files); # paths of files that changed, for info - sass.compile() -""" - def app_watch_handler(watched_app_subpaths): + """Finds files to watch for changes""" stack = inspect.stack invoker = pathlib.Path(stack()[1].filename) apps_path = pathlib.Path(os.environ["PY4WEB_APPS_FOLDER"]) app = invoker.relative_to(os.environ["PY4WEB_APPS_FOLDER"]).parts[0] def decorator(func): - handler = "{}.{}".format(func.__module__, func.__name__) + handler = f"{func.__module__}.{func.__name__}" APP_WATCH["handlers"][handler] = func for subpath in watched_app_subpaths: app_path = apps_path.joinpath(app, subpath).as_posix() @@ -1481,6 +1575,7 @@ def decorator(func): def try_app_watch_tasks(): + """If there are watch tasks, executes them when files change""" if APP_WATCH["tasks"]: tried_tasks = [] for handler in APP_WATCH["tasks"]: @@ -1488,7 +1583,7 @@ def try_app_watch_tasks(): try: APP_WATCH["handlers"][handler](changed_files_dict.keys()) tried_tasks.append(handler) - except Exception: + except Exception: # pylint: disable=broad-exception-caught logging.error(traceback.format_exc()) ## remove executed tasks from register for handler in tried_tasks: @@ -1496,19 +1591,32 @@ def try_app_watch_tasks(): def watch(apps_folder, server_config, mode="sync"): + """Watches files for change""" + def watch_folder_event_loop(apps_folder): + """Main event loop looking for file changes""" loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(watch_folder(apps_folder)) async def watch_folder(apps_folder): - click.echo( - "watching (%s-mode) python file changes in: %s" % (mode, apps_folder) - ) + """Async function that watches a folder for changes""" + click.echo(f"watching ({mode}-mode) python file changes in: {apps_folder}") + + # Then load all the apps as submodules + if os.environ.get("PY4WEB_APP_NAMES"): + app_names = os.environ.get("PY4WEB_APP_NAMES").split(",") + else: + app_names = None + async for changes in awatch(os.path.join(apps_folder)): apps = [] for subpath in [pathlib.Path(pair[1]) for pair in changes]: name = subpath.relative_to(apps_folder).parts[0] + # ignore apps not listed in app names + if app_names is not None and name not in app_names: + continue + # record the name of the app that changed if subpath.suffix == ".py": apps.append(name) ## manage `app_watch_handler` decorators @@ -1519,11 +1627,13 @@ async def watch_folder(apps_folder): APP_WATCH["tasks"][handler] = {} APP_WATCH["tasks"][handler][subpath.as_posix()] = True + # reimport the apps the changed for name in apps: if mode == "lazy": DIRTY_APPS[name] = True else: Reloader.import_app(name) + ## in 'lazy' mode it's done in bottle's 'before_request' hook if mode != "lazy": try_app_watch_tasks() @@ -1531,7 +1641,8 @@ async def watch_folder(apps_folder): if server_config["number_workers"] > 1: click.echo("--watch option has no effect in multi-process environment \n") return - elif server_config["server"].startswith(("wsgiref", "waitress", "rocket")): + + if server_adapters.blocking.get(server_config["server"]): # these servers block the main thread so we open a new thread for the file watcher threading.Thread( target=watch_folder_event_loop, args=(apps_folder,), daemon=True @@ -1553,10 +1664,11 @@ async def watch_folder(apps_folder): def log_routes(apps_routes, out_file="routes-py4web.txt"): + """Logs defined routes to a file""" tmp = os.environ.get("TEMPDIR", "/tmp") path_out_file = os.path.join(tmp, out_file) try: - with open(path_out_file, "w") as f: + with open(path_out_file, "w", encoding="utf8") as f: f.write( "\n".join( [ @@ -1571,6 +1683,7 @@ def log_routes(apps_routes, out_file="routes-py4web.txt"): def start_server(kwargs): + """Starts the web server""" host = kwargs["host"] port = int(kwargs["port"]) apps_folder = kwargs["apps_folder"] @@ -1588,7 +1701,7 @@ def start_server(kwargs): if not server_config["server"]: if server_config["platform"] == "windows" or number_workers < 2: - server_config["server"] = "rocketServer" + server_config["server"] = "rocket" else: if not gunicorn: logging.error("gunicorn not installed") @@ -1596,20 +1709,13 @@ def start_server(kwargs): server_config["server"] = "gunicorn" # Catch interrupts like Ctrl-C if needed - if server_config["server"] not in {"rocketServer", "wsgirefWsTwistedServer"}: - signal.signal( - signal.SIGINT, - lambda signal, frame: click.echo( - "KeyboardInterrupt (ID: {}) has been caught. Cleaning up...".format( - signal - ) - and sys.exit(0), - ), - ) + def kill_all(sig, _): + os.kill(os.getpid(), signal.SIGKILL) - params["server"] = server_config["server"] - if params["server"] in server_adapters.__all__: - params["server"] = getattr(server_adapters, params["server"])() + signal.signal(signal.SIGINT, kill_all) + + adapter = server_adapters.available.get(server_config["server"]) + params["server"] = adapter or server_config["server"] if number_workers > 1: params["workers"] = number_workers if server_config["server"] == "gunicorn": @@ -1622,7 +1728,7 @@ def start_server(kwargs): if kwargs["watch"] != "off": print("Error: watch doesn't work with gevent. ") print("invoke py4web with `--watch off` or choose another server. ") - exit(255) + sys.exit(255) if not hasattr(_ssl, "sslwrap"): _ssl.sslwrap = new_sslwrap @@ -1635,13 +1741,12 @@ def start_server(kwargs): bottle.run(**params) - -def check_compatible(version): +def check_compatible(py4web_version): """To be called by apps to check if module version is compatible with py4web requirements""" - from . import __version__ + from . import __version__ # pylint: disable=import-outside-toplevel return tuple(map(int, __version__.split("."))) >= tuple( - map(int, version.split(".")) + map(int, py4web_version.split(".")) ) @@ -1676,6 +1781,7 @@ def __init__(self, pkg, pkg_alias="apps"): sys.meta_path.append(self) def find_spec(self, fullname, path=None, target=None): + """Loads the spec for the module at fullname""" if fullname == self.pkg_alias and path is None: spec = importlib.util.find_spec(self.pkg) if spec: @@ -1684,9 +1790,11 @@ def find_spec(self, fullname, path=None, target=None): fullname, spec.origin ) return spec + return None -def install_args(kwargs, reinstall_apps=False): +def install_args(kwargs, reinstall_apps=False): # pylint: disable=too-many-statements + """Handles the command line argumens and adds them to the os.environ""" # always convert apps_folder to an absolute path apps_folder = kwargs["apps_folder"] = os.path.abspath(kwargs["apps_folder"]) kwargs["service_folder"] = os.path.join( @@ -1695,14 +1803,17 @@ def install_args(kwargs, reinstall_apps=False): kwargs["service_db_uri"] = DEFAULTS["PY4WEB_SERVICE_DB_URI"] for key, val in kwargs.items(): os.environ["PY4WEB_" + key.upper()] = str(val) - Fixture.__fixture_debug__ = kwargs.get("debug", False) + + global DEBUG # pylint: disable=global-statement + DEBUG = kwargs.get("debug", False) + logging.getLogger().setLevel( - 0 if Fixture.__fixture_debug__ else kwargs.get("logging_level", logging.WARNING) + 0 if DEBUG else kwargs.get("logging_level", logging.WARNING) ) yes2 = yes = kwargs.get("yes", False) # If the apps folder does not exist create it and populate it if not os.path.exists(apps_folder): - if yes or click.confirm("Create missing folder %s?" % apps_folder): + if yes or click.confirm(f"Create missing folder {apps_folder}?"): os.makedirs(apps_folder) yes2 = True else: @@ -1710,9 +1821,9 @@ def install_args(kwargs, reinstall_apps=False): sys.exit(0) init_py = os.path.join(apps_folder, "__init__.py") if not os.path.exists(init_py): - if yes2 or click.confirm("Create missing init file %s?" % init_py): + if yes2 or click.confirm(f"Create missing init file {init_py}?"): with open(init_py, "wb"): - pass + ... else: click.echo("Command aborted") sys.exit(0) @@ -1727,13 +1838,13 @@ def install_args(kwargs, reinstall_apps=False): os.mkdir(kwargs["service_folder"]) session_secret_filename = os.path.join(kwargs["service_folder"], "session.secret") if not os.path.exists(session_secret_filename): - with open(session_secret_filename, "w") as fp: + with open(session_secret_filename, "w", encoding="utf8") as fp: fp.write(str(uuid.uuid4())) - with open(session_secret_filename) as fp: + with open(session_secret_filename, "r", encoding="utf8") as fp: Session.SECRET = fp.read() - # after everything is etup but before installing apps, init + # after everything is setup but before installing apps, init error_logger.initialize() # Reinstall apps from zipped ones in assets @@ -1748,8 +1859,8 @@ def install_args(kwargs, reinstall_apps=False): app_name = filename.split(".")[-2] target_dir = os.path.join(apps_folder, app_name) if not os.path.exists(target_dir): - if yes or click.confirm("Create app %s?" % app_name): - click.echo("[ ] Unzipping app %s" % filename) + if yes or click.confirm(f"Create app {app_name}?"): + click.echo(f"[ ] Unzipping app {filename}") with zipfile.ZipFile(zip_filename, "r") as zip_file: os.makedirs(target_dir) zip_file.extractall(target_dir) @@ -1770,28 +1881,28 @@ def wsgi(**kwargs): @click.group( context_settings=dict(help_option_names=["-h", "-help", "--help"]), - help='%s\n\nType "%s COMMAND -h" for available options on commands' - % (__doc__, PY4WEB_CMD), + help=f'{__doc__}\n\nType "{PY4WEB_CMD} COMMAND -h" for available options on commands', ) def cli(): - pass + """The Command Line Interface""" @cli.command() @click.option( - "-a", "--all", is_flag=True, default=False, help="List version of all modules" + "-v", "--verbose", is_flag=True, default=False, help="List version of all modules" ) -def version(all): +def version(verbose=False): """Show versions and exit""" - from . import __version__ + from . import __version__ # pylint: disable=import-outside-toplevel - click.echo("py4web: %s" % __version__) - if all: - click.echo("system: %s" % platform.platform()) - click.echo("python: %s" % sys.version.replace("\n", " ")) + click.echo(f"py4web: {__version__}") + if verbose: + sys_version = sys.version.replace("\n", " ") + click.echo(f"system: {platform.platform()}") + click.echo(f"python: {sys_version}") for name in sorted(sys.modules): if hasattr(sys.modules[name], "__version__"): - click.echo("%s: %s" % (name, sys.modules[name].__version__)) + click.echo(f"{name}: {sys.modules[name].__version__}") @cli.command() @@ -1823,7 +1934,7 @@ def shell(**kwargs): """Open a python shell with apps_folder's parent added to the path""" if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): # running in the PyInstaller binary bundle - import site + import site # pylint: disable=possibly-unused-variable,import-outside-toplevel install_args(kwargs) code.interact(local=dict(globals(), **locals())) @@ -1851,9 +1962,9 @@ def call(apps_folder, func, yes, args): install_args(dict(apps_folder=apps_folder, yes=yes)) apps_folder_name = os.path.basename(os.environ["PY4WEB_APPS_FOLDER"]) app_name = func.split(".")[0] - module, name = ("%s.%s" % (apps_folder_name, func)).rsplit(".", 1) + module, name = f"{apps_folder_name}.{func}".rsplit(".", 1) env = {} - exec("from %s import %s" % (module, name), {}, env) + exec(f"from {module} import {name}", {}, env) # pylint: disable=exec-used request.app_name = app_name env[name](**kwargs) @@ -1875,8 +1986,8 @@ def call(apps_folder, func, yes, args): ) def set_password(password, password_file): """Set administrator's password for the Dashboard""" - click.echo('Storing the hashed password in file "%s"\n' % password_file) - with open(password_file, "w") as fp: + click.echo(f'Storing the hashed password in file "{password_file}"\n') + with open(password_file, "w", encoding="utf8") as fp: fp.write(str(pydal.validators.CRYPT()(password)[0])) @@ -1906,15 +2017,14 @@ def new_app(apps_folder, app_name, yes, scaffold_zip): ) target_dir = os.path.join(os.environ["PY4WEB_APPS_FOLDER"], app_name) if not os.path.exists(source): - click.echo("Source app %s does not exists" % source) + click.echo(f"Source app {source} does not exists") sys.exit(1) elif os.path.exists(target_dir): - click.echo("Target folder %s already exists" % target_dir) + click.echo(f"Target folder {target_dir} already exists") sys.exit(1) else: - zfile = zipfile.ZipFile(source, "r") - zfile.extractall(target_dir) - zfile.close() + with zipfile.ZipFile(source, "r") as zfile: + zfile.extractall(target_dir) @cli.command() @@ -1927,7 +2037,9 @@ def new_app(apps_folder, app_name, yes, scaffold_zip): help="No prompt, assume yes to questions", show_default=True, ) -@click.option("-H", "--host", default="127.0.0.1", help="Host name", show_default=True) +@click.option( + "-H", "--host", default="127.0.0.1", help="Host listening IP", show_default=True +) @click.option( "-P", "--port", default=8000, type=int, help="Port number", show_default=True ) @@ -1935,7 +2047,7 @@ def new_app(apps_folder, app_name, yes, scaffold_zip): "-A", "--app_names", default="", - help="List of apps to run (all if omitted or empty)", + help="List of apps to run, comma separated (all if omitted or empty)", ) @click.option( "-p", @@ -1965,11 +2077,12 @@ def new_app(apps_folder, app_name, yes, scaffold_zip): "--server", default="default", type=click.Choice( - ["default", "wsgiref", "tornado", "gunicorn", "gevent", "waitress"] - + server_adapters.__all__ + ["default"] + list(server_adapters.available) + server_adapters.unavailable ), - help="server to use", - show_default=True, + help="Web server to use (unavailable: {})".format( + ", ".join(server_adapters.unavailable) + ), + show_default=False, ) @click.option( "-w", @@ -2007,7 +2120,7 @@ def new_app(apps_folder, app_name, yes, scaffold_zip): "-L", "--logging_level", type=int, - default=logging.WARNING, + default=logging.INFO, help="The log level (0 - 50) [default: 30 (=WARNING)]", ) @click.option( @@ -2025,14 +2138,21 @@ def new_app(apps_folder, app_name, yes, scaffold_zip): help="Prefix to add to all URLs in and out", show_default=True, ) +@click.option( + "-m", + "--mode", + default="default", + help="default or development", + show_default=True, +) def run(**kwargs): - """Run all the applications on apps_folder""" + """Run the applications on apps_folder""" install_args(kwargs) - from py4web import __version__ + from py4web import __version__ # pylint: disable=import-outside-toplevel click.secho(ART, fg="blue") - click.echo("Py4web: %s on Python %s\n\n" % (__version__, sys.version)) + click.echo(f"Py4web: {__version__} on Python {sys.version}\n\n") # Start Reloader.import_apps() @@ -2043,10 +2163,11 @@ def run(**kwargs): kwargs["password_file"] ): click.echo( - 'You have not set a dashboard password. Run "%s set_password" to do so.' - % PY4WEB_CMD + f'\nYou have not set a dashboard password. Run "{PY4WEB_CMD} set_password" to do so.' ) - elif "_dashboard" in Reloader.ROUTES: + elif "_dashboard" in Reloader.ROUTES and ( + not kwargs["host"].startswith("unix:/") + ): click.echo( f"Dashboard is at: http{'s' if kwargs.get('ssl_cert', None) else ''}://{kwargs['host']}:{kwargs['port']}/_dashboard" ) diff --git a/py4web/gunicorn.rst b/py4web/gunicorn.rst new file mode 100644 index 000000000..c080392e6 --- /dev/null +++ b/py4web/gunicorn.rst @@ -0,0 +1,209 @@ +==================== +gunicorn and py4web +==================== + + +The gunicorn server starts in the usual way for the py4web + +:: + + $./py4web.py run apps -s gunicorn --watch=off + $ + $./py4web.py run apps -H 'unix:/tmp/p4w.sock' -w 4 -L 10 + $ + $./py4web.py run apps -s gunicornGevent --watch=off + + +gunicornGevent === gunicorn + monkey.patch_all() + +It is possible to use several methods to configure gunicorn options with py4web + +Let's show examples (go to py4web root dir) + + +* set gunicorn options via py4web keys + + use: -H, -P, -w, -L, --ssl_cert, --ssl_key , -Q + + ./py4web.py run apps -s gunicorn --watch=off -H 192.168.1.161 -P 9000 -L 20 --ssl_cert=cert.pem --ssl_key=key.pem + + with -L 10 we can see gunicorn options in server-py4web.log + + this is enough for regular applications (you don't have to read further) + +* set gunicorn options via bash env variables + + :: + + $export GUNICORN_worker_class=sync + $ ./py4web.py run apps -s gunicorn -L 20 -w 4 --watch=off + $ + $export GUNICORN_worker_class=gthread + $ ./py4web.py run apps -s gunicorn -L 20 -w 4 --watch=off + $ + $export GUNICORN_worker_class=gevent + $ ./py4web.py run apps -s gunicornGevent -L 20 -w 4 --watch=off + $ + $export GUNICORN_worker_class=eventlet + $ ./py4web.py run apps -s gunicornGevent -L 20 -w 4 --watch=off + + + + +* set gunicorn options via config file gunicorn.saenv + + :: + + # gunicorn.saenv: example file + # + # its key=value file + # export GUNICORN_ will be removed + # + # boolean + # print_config=False + # + # str + export GUNICORN_raw_env=VARIABLE_HERE=VARIABLE_VALUE_HERE, v2=x2, + # python dict + export GUNICORN_secure_scheme_headers={'x':'x1', 'y':'y1',} + # None + certfile=None + + export GUNICORN_worker_tmp_dir=/dev/shm + export GUNICORN_max_requests=1200 + worker_class=gthread + threads=2 + + # gunicornGevent + #worker_class=gevent + #worker_class=eventlet + + # for use python-config-file + # use_python_config=myguni.conf.py + # or short + # usepy=myguni.conf.py + + # for use python-config-mod_name + # use_python_config=python:mod_name + + +* set gunicorn options via python file myguni.conf.py + + :: + + set the env variable use_python_config=myguni.conf.py + + .. code:: bash + + $ # via env + $export GUNCORN_use_python_config=myguni.conf.py + $ + $ # via gunicorn.saenv + $echo use_python_config=mmyguni.conf.py >> gunicorn.saenv + + :: + + write file myguni.conf.py + + .. code:: python + + # myguni.conf.py : example gunicorn configuration file + # https://docs.gunicorn.org/en/stable/settings.html + + import multiprocessing + + max_requests = 1000 + max_requests_jitter = 50 + + log_file = "-" + + workers = multiprocessing.cpu_count() * 2 + 1 + + :: + + $ ./py4web.py run apps -s gunicorn --watch=off + + +* set gunicorn options via python module + + :: + + create a new python module mod_name + + .. code:: bash + + + $ mkdir mod_name && cp myguni.conf.py mod_name/__init__.py + $ + $ # via env + $export GUNCORN_use_python_config=python:mod_name + $ + $ # via gunicorn.saenv + $echo use_python_config=python:mod_name >> gunicorn.saenv + + + :: + + $ ./py4web.py run apps -s gunicorn --watch=off + + +* set gunicorn options via gunicorn.conf.py + + :: + + + write gunicorn settings to the gunicorn.conf.py + + (if gunicorn.conf.py exists, the GUNICORN_ vars and the file gunicorn.saenv will be ignored) + + .. code:: bash + + $ echo "print_config = True" > gunicorn.conf.py + $ # or + $ cp myguni.con.py gunicorn.conf.py + + + :: + + $ ./py4web.py run apps -s gunicorn --watch=off + +* set gunicorn options via gunicorn-cli + + :: + + run py4web/apps as wsgi-apps + + .. code:: bash + + $ echo 'from py4web.core import wsgi;myapp = wsgi(apps_folder="apps")' > py4web_wsgi.py + $ + + + :: + + $ gunicorn -w 4 py4web_wsgi:myapp + + +* test gunicorn response time + + :: + + add to .bashrc + + .. code:: bash + + export PY4WEB_LOGS=/tmp + p4w_srv_test() { time seq 1 500 | xargs -I % curl http://localhost:8000/todo &>/dev/null ;} + gunitest() { for ((i=0; i < 20; i++)); do p4w_srv_test & done ;} + + :: + + $ ./py4web.py run apps -s gunicorn -L 10 --watch=off & + + $ tguni + $ + $ less /tmp/server-py4web.log + + +thats it + diff --git a/py4web/server_adapters.py b/py4web/server_adapters.py deleted file mode 100644 index 1b0953054..000000000 --- a/py4web/server_adapters.py +++ /dev/null @@ -1,329 +0,0 @@ -import logging -import os -import ssl - -from ombott.server_adapters import ServerAdapter - -try: - from .utils.wsservers import * -except ImportError: - wsservers_list = [] - -__all__ = [ - "gevent", - "geventWebSocketServer", - "wsgirefThreadingServer", - "rocketServer", -] + wsservers_list - - -def check_level(level): - - # lib/python3.7/logging/__init__.py - # CRITICAL = 50 - # FATAL = CRITICAL - # ERROR = 40 - # WARNING = 30 - # WARN = WARNING - # INFO = 20 - # DEBUG = 10 - # NOTSET = 0 - - return ( - level - if level - in ( - logging.CRITICAL, - logging.ERROR, - logging.WARN, - logging.INFO, - logging.DEBUG, - logging.NOTSET, - ) - else logging.WARN - ) - - -def logging_conf(level, log_file="server-py4web.log"): - - # export PY4WEB_LOGS=/tmp # set log_file directory - - log_dir = os.environ.get("PY4WEB_LOGS", None) - - log_param = { - "format":"%(threadName)s | %(message)s", - "level":check_level(level), - } - - if log_dir: - h = logging.FileHandler( - os.path.join( log_dir, log_file), - mode = "w", - encoding = "utf-8" - ) - log_param.update( {"handlers": [h]} ) - - logging.basicConfig(**log_param) - - -def get_workers(opts, default=10): - try: - return opts["workers"] if opts["workers"] else default - except KeyError: - return default - - -def gevent(): - # gevent version 22.10.2 - - import threading - - from gevent import local, pywsgi # pip install gevent - - if not isinstance(threading.local(), local.local): - msg = "Ombott requires gevent.monkey.patch_all() (before import)" - raise RuntimeError(msg) - - # ./py4web.py run apps --watch=off -s gevent -L 20 - # - # ./py4web.py run apps -s gevent --watch=off --port=8443 --ssl_cert=cert.pem --ssl_key=key.pem -L 0 - # ./py4web.py run apps -s gevent --watch=off --host=192.168.1.161 --port=8443 --ssl_cert=server.pem -L 0 - - class GeventServer(ServerAdapter): - def run(self, handler): - - logger = "default" # not None - from gevent doc - if not self.quiet: - - logger = logging.getLogger("gevent") - log_dir = os.environ.get("PY4WEB_LOGS", None) - fh = ( - logging.FileHandler(None) - if not log_dir - else ( - logging.FileHandler(os.path.join(log_dir, "server-py4web.log")) - ) - ) - logger.setLevel(check_level(self.options["logging_level"])) - logger.addHandler(fh) - logger.addHandler(logging.StreamHandler()) - - certfile = self.options.get("certfile", None) - - ssl_args = ( - dict( - certfile=certfile, - keyfile=self.options.get("keyfile", None), - ssl_version=ssl.PROTOCOL_SSLv23, - server_side=True, - do_handshake_on_connect=False, - ) - if certfile - else dict() - ) - - server = pywsgi.WSGIServer( - (self.host, self.port), - handler, - log=logger, - error_log=logger, - **ssl_args - ) - - server.serve_forever() - - return GeventServer - - -def geventWebSocketServer(): - from gevent import pywsgi - - # from geventwebsocket.handler import WebSocketHandler # pip install gevent-websocket - from gevent_ws import WebSocketHandler # pip install gevent gevent-ws - - # https://stackoverflow.com/questions/5312311/secure-websockets-with-self-signed-certificate - # https://pypi.org/project/gevent-ws/ - # ./py4web.py run apps -s geventWebSocketServer --watch=off --ssl_cert=server.pem -H 192.168.1.161 -P 9000 -L 10 - # vi apps/_websocket/templates/index.html set: ws, wss, host, port - # firefox http://localhost:8000/_websocket - # firefox https://192.168.1.161:9000/_websocket test wss - # curl --insecure -I -H 'Upgrade: websocket' \ - # -H "Sec-WebSocket-Key: `openssl rand -base64 16`" \ - # -H 'Sec-WebSocket-Version: 13' \ - # -sSv https://192.168.1.161:9000/ - - class GeventWebSocketServer(ServerAdapter): - def run(self, handler): - logger = "default" # not None !! from gevent doc - if not self.quiet: - logging_conf( - self.options["logging_level"], - ) - logger = logging.getLogger("gevent-ws") - - certfile = self.options.get("certfile", None) - - ssl_args = ( - dict( - certfile=certfile, - keyfile=self.options.get("keyfile", None), - ) - if certfile - else dict() - ) - - server = pywsgi.WSGIServer( - (self.host, self.port), - handler, - handler_class=WebSocketHandler, - log=logger, - error_log=logger, - **ssl_args - ) - - server.serve_forever() - - return GeventWebSocketServer - - -def wsgirefThreadingServer(): - # https://www.electricmonk.nl/log/2016/02/15/multithreaded-dev-web-server-for-the-python-bottle-web-framework/ - - import socket - from concurrent.futures import ThreadPoolExecutor # pip install futures - from socketserver import ThreadingMixIn - from wsgiref.simple_server import WSGIRequestHandler, WSGIServer, make_server - - class WSGIRefThreadingServer(ServerAdapter): - def run(self, app): - - if not self.quiet: - - logging_conf( - self.options["logging_level"], - ) - self.log = logging.getLogger("WSGIRef") - - self_run = self # used in internal classes to access options and logger - - class PoolMixIn(ThreadingMixIn): - def process_request(self, request, client_address): - self.pool.submit( - self.process_request_thread, request, client_address - ) - - class ThreadingWSGIServer(PoolMixIn, WSGIServer): - daemon_threads = True - pool = ThreadPoolExecutor( - max_workers=get_workers(self.options, default=40) - ) - - class Server: - def __init__( - self, server_address=("127.0.0.1", 8000), handler_cls=None - ): - self.wsgi_app = None - self.listen, self.port = server_address - self.handler_cls = handler_cls - - def set_app(self, app): - self.wsgi_app = app - - def get_app(self): - return self.wsgi_app - - def serve_forever(self): - self.server = make_server( - self.listen, - self.port, - self.wsgi_app, - ThreadingWSGIServer, - self.handler_cls, - ) - - # openssl req -newkey rsa:4096 -new -x509 -keyout server.pem -out server.pem -days 365 -nodes - # openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 - # ./py4web.py run apps -s wsgirefThreadingServer --watch=off --port=8443 --ssl_cert=cert.pem --ssl_key=key.pem - # openssl s_client -showcerts -connect 127.0.0.1:8443 - - certfile = self_run.options.get("certfile", None) - - if certfile: - self.server.socket = ssl.wrap_socket( - self.server.socket, - certfile=certfile, - keyfile=self_run.options.get("keyfile", None), - ssl_version=ssl.PROTOCOL_SSLv23, - server_side=True, - do_handshake_on_connect=False, - ) - - self.server.serve_forever() - - class LogHandler(WSGIRequestHandler): - def address_string(self): # Prevent reverse DNS lookups please. - return self.client_address[0] - - def log_request(self, *args, **kw): - if not self_run.quiet: - return WSGIRequestHandler.log_request(self, *args, **kw) - - def log_message(self, format, *args): - if not self_run.quiet: # and ( not args[1] in ['200', '304']) : - msg = "%s - - [%s] %s" % ( - self.client_address[0], - self.log_date_time_string(), - format % args, - ) - self_run.log.info(msg) - - handler_cls = self.options.get("handler_class", LogHandler) - server_cls = Server - - if ":" in self.host: # Fix wsgiref for IPv6 addresses. - if getattr(server_cls, "address_family") == socket.AF_INET: - - class ServerClass(Server): - address_family = socket.AF_INET6 - - server_cls = ServerClass - - srv = make_server(self.host, self.port, app, server_cls, handler_cls) - srv.serve_forever() - - return WSGIRefThreadingServer - - -def rocketServer(): - try: - from rocket3 import Rocket3 as Rocket - except ImportError: - from .rocket3 import Rocket3 as Rocket - - class RocketServer(ServerAdapter): - def run(self, app): - - if not self.quiet: - - logging_conf( - self.options["logging_level"], - ) - - interface = ( - ( - self.host, - self.port, - self.options["keyfile"], - self.options["certfile"], - ) - if ( - self.options.get("certfile", None) - and self.options.get("keyfile", None) - ) - else (self.host, self.port) - ) - - server = Rocket(interface, "wsgi", dict(wsgi_app=app)) - server.start() - - return RocketServer diff --git a/py4web/server_adapters/__init__.py b/py4web/server_adapters/__init__.py new file mode 100644 index 000000000..8b294ca7e --- /dev/null +++ b/py4web/server_adapters/__init__.py @@ -0,0 +1,78 @@ +__all__ = ["available", "unavailable", "blocking"] + +unavailable = [] +available = {} +blocking = {} + +# Web servers supported natively by ombott + +available["wsgiref"] = "wsgiref" +blocking["wsgiref"] = True + +try: + import tornado + + available["tornado"] = "tornado" + blocking["tornado"] = False +except ModuleNotFoundError: + unavailable.append("tornado") + + +try: + import waitress + + available["waitress"] = "waitress" + blocking["waitress"] = True +except ModuleNotFoundError: + unavailable.append("waitress") + +try: + import gunicorn + + available["gunicorn"] = "gunicorn" + blocking["gunicorn"] = False +except ModuleNotFoundError: + unavailable.append("gunicorn") + +# additional custom adaptrs + +try: + from .adapter_wsgiref import WSGIRefAdapter + + available["wsgiref+threaded"] = WSGIRefAdapter + blocking["wsgiref+threaded"] = True +except ModuleNotFoundError: + unavailable.append("wsgiref+threaded") + +try: + from .adapter_rocket3 import Rocket3Adapter + + available["rocket"] = Rocket3Adapter + blocking["rocket"] = True +except ModuleNotFoundError: + unavailable.append("rocket") + +try: + from .adapter_gevent import GeventAdapter + + available["gevent"] = GeventAdapter + blocking["gevent"] = False +except ModuleNotFoundError: + unavailable.append("gevent") + +try: + from .adapter_gunicorn_gevent import GunicornGeventAdapter + + available["gunicorn+gevent"] = GunicornGeventAdapter + blocking["gunicorn+gevent"] = False +except ModuleNotFoundError: + unavailable.append("gunicorn+gevent") + +try: + from .adapter_gevent_websockets import GeventWebsocketsAdapter + + available["gevent+websockets"] = GeventWebsocketsAdapter + blocking["gevent+websockets"] = False + +except ModuleNotFoundError: + unavailable.append("gevent+websockets") diff --git a/py4web/server_adapters/adapter_gevent.py b/py4web/server_adapters/adapter_gevent.py new file mode 100644 index 000000000..afc113a29 --- /dev/null +++ b/py4web/server_adapters/adapter_gevent.py @@ -0,0 +1,56 @@ +import ssl +import threading + +from gevent import local, pywsgi # pip install gevent +from ombott.server_adapters import ServerAdapter + +from .logging_utils import logging_conf + +# ./py4web.py run apps --watch=off -s gevent -L 20 +# +# ./py4web.py run apps -s gevent --watch=off --port=8443 --ssl_cert=cert.pem --ssl_key=key.pem -L 0 +# ./py4web.py run apps -s gevent --watch=off --host=192.168.1.161 --port=8443 --ssl_cert=server.pem -L 0 + + +class GeventAdapter(ServerAdapter): + "Defines a gevent server" + + def run(self, handler): + "runs the server" + + if not isinstance(threading.local(), local.local): + msg = "Ombott requires gevent.monkey.patch_all() (before import)" + raise RuntimeError(msg) + + logger = None # "default" + + if not self.quiet: + logger = logging_conf( + self.options["logging_level"], + "gevent", + ) + # logger.addHandler(logging.StreamHandler()) + + certfile = self.options.get("certfile", None) + + ssl_args = ( + dict( + certfile=certfile, + keyfile=self.options.get("keyfile", None), + ssl_version=ssl.PROTOCOL_SSLv23, + server_side=True, + do_handshake_on_connect=False, + ) + if certfile + else {} + ) + + server = pywsgi.WSGIServer( + (self.host, self.port), + handler, + log=logger, + error_log=logger, + **ssl_args, + ) + + server.serve_forever() diff --git a/py4web/server_adapters/adapter_gevent_websockets.py b/py4web/server_adapters/adapter_gevent_websockets.py new file mode 100644 index 000000000..a06f03ed4 --- /dev/null +++ b/py4web/server_adapters/adapter_gevent_websockets.py @@ -0,0 +1,46 @@ +# from geventwebsocket.handler import WebSocketHandler # pip install gevent-websocket +from gevent import pywsgi +from gevent_ws import WebSocketHandler # pip install gevent gevent-ws +from ombott.server_adapters import ServerAdapter + +from .logging_utils import logging_conf + +# https://stackoverflow.com/questions/5312311/secure-websockets-with-self-signed-certificate +# https://pypi.org/project/gevent-ws/ +# ./py4web.py run apps -s geventWebSocketServer --watch=off --ssl_cert=server.pem -H 192.168.1.161 -P 9000 -L 10 +# vi apps/_websocket/templates/index.html set: ws, wss, host, port +# firefox http://localhost:8000/_websocket +# firefox https://192.168.1.161:9000/_websocket test wss +# curl --insecure -I -H 'Upgrade: websocket' \ +# -H "Sec-WebSocket-Key: `openssl rand -base64 16`" \ +# -H 'Sec-WebSocket-Version: 13' \ +# -sSv https://192.168.1.161:9000/ + + +class GeventWebSocketAdapter(ServerAdapter): + "Class implementing a Gevent websocket server" + + def run(self, handler): + "Runs the server" + logger = None # "default" + + if not self.quiet: + logger = logging_conf( + self.options["logging_level"], + "gevent-ws", + ) + + args = dict( + handler_class=WebSocketHandler, + log=logger, + error_log=logger, + ) + + certfile = self.options.get("certfile") + keyfile = self.options.get("keyfile") + if certfile and keyfile: + args.update(certfile=certfile, keyfile=keyfile) + + server = pywsgi.WSGIServer((self.host, self.port), handler, **args) + + server.serve_forever() diff --git a/py4web/server_adapters/adapter_gunicorn_gevent.py b/py4web/server_adapters/adapter_gunicorn_gevent.py new file mode 100644 index 000000000..ddbd40168 --- /dev/null +++ b/py4web/server_adapters/adapter_gunicorn_gevent.py @@ -0,0 +1,191 @@ +import ast +import errno +import logging +import os +import socket +import subprocess +import sys +import threading + +from gevent import local # pip install gevent gunicorn setproctitle +from ombott.server_adapters import ServerAdapter + +from .logging_utils import check_level, get_log_file, logging_conf + + +def check_port(host="127.0.0.1", port=8000): + "Check the specified port is available and print debug info" + + if host.startswith("unix:/"): + socket_path = host[5:] + if os.path.exists(socket_path): + if port == 0: + if ( + subprocess.run( + ["ls", "-alFi", "socket_path"], shell=False, check=False + ).returncode + != 0 + ): + sys.exit(f"can't run gunicorn: {socket_path} exists") + elif port == 1: + subprocess.run( + "ps -ef | head -1; ps -ef | grep py4web | grep -v grep", + shell=True, + check=False, + ) + subprocess.run(["ls", "-alFi", socket_path], shell=False, check=False) + subprocess.run(["lsof", "-w", socket_path], shell=False, check=False) + elif port == 8000: + pass + print(f"gunicorn listening at: {host}") + return + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind((host, int(port))) + except socket.error as e: + if e.errno == errno.EADDRINUSE: + subprocess.run( + f"command -v lsof >/dev/null 2>&1 && ps -ef | head -1; ps -ef |" + f" grep py4web | grep -v grep && lsof -nPi:{port}", + shell=True, + check=False, + ) + sys.exit(f"{host}:{port} is already in use") + else: + sys.exit(f"{e}\n{host}:{port} cannot be acessed") + s.close() + + +def check_kv(kx, vx): + "convenience function" + if kx and vx and kx not in ("bind", "config"): + if vx.startswith("{") and vx.endswith("}"): + vx = ast.literal_eval(vx) + if vx == "None": + vx = None + return kx, vx + return None, None + + +def get_gunicorn_options( + gu_default="gunicorn.conf.py", + env_file="gunicorn.saenv", + env_key="GUNICORN_", +): + "Returns the default options" + if os.path.isfile(gu_default): + return {"use_python_config": gu_default, "config": gu_default} + + res_opts = {} + + if os.path.isfile(env_file): + with open(env_file, "r") as f: + lines = f.read().splitlines() + for line in lines: + line = line.strip() + if not line or line.startswith(("#", "[")): + continue + for e in ("export ", env_key): + line = line.replace(e, "", 1) + k, v = None, None + try: + k, v = line.split("=", 1) + k, v = k.strip().lower(), v.strip() + except (ValueError, AttributeError): + continue + k, v = check_kv(k, v) + if k is None: + continue + res_opts[k] = v + + if res_opts: + res_opts["config"] = env_file + return res_opts + + for k, v in os.environ.items(): + if k.startswith(env_key): + k = k.split("_", 1)[1].lower() + k, v = check_kv(k, v) + if k is None: + continue + res_opts[k] = v + + if res_opts: + res_opts["config"] = env_key + + return res_opts + + +class GunicornGeventAdapter(ServerAdapter): + "The gunicorn server adapter" + + def run(self, handler): + "runs the server" + + if isinstance(threading.local(), local.local): + print("gunicorn: monkey.patch_all() applied") + + try: + from gunicorn.app.base import Application + except ImportError as ex: + sys.exit(f"{ex}\nTry: pip install gunicorn gevent setproctitle") + + check_port(self.host, self.port) + + logger = None + + sa_bind = ( + self.host if self.host.startswith("unix:/") else f"{self.host}:{self.port}" + ) + + sa_config = { + "bind": sa_bind, # f"{self.host}:{self.port}", + "workers": self.options.get("workers", 10), + "certfile": self.options.get("certfile", None), + "keyfile": self.options.get("keyfile", None), + "accesslog": None, + "errorlog": None, + "proc_name": "sa_py4web", # ps a | grep py4web + "config": "sa_config", + # ( 'sa_config', 'GUNICORN_', 'gunicorn.saenv', 'gunicorn.conf.py' ) + } + + if not self.quiet: + level = check_level(self.options["logging_level"]) + log_file = get_log_file() + + logger = logging_conf(level) + log_to = "-" if log_file is None else log_file + + sa_config.update( + { + "loglevel": logging.getLevelName(level), + "access_log_format": '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"', + "accesslog": log_to, + "errorlog": log_to, + } + ) + + class GunicornApplication(Application): + "A gunicorn application" + + def load_config(self): + "Loads the config" + sa_config.update(get_gunicorn_options()) + logger and logger.debug(sa_config) + + for k, v in sa_config.items(): + if k not in self.cfg.settings: + continue + self.cfg.set(k, v) + + for key in ("use_python_config", "usepy"): + if key in sa_config: + Application.load_config_from_file(self, sa_config[key]) + break + + def load(self): + return handler + + GunicornApplication().run() diff --git a/py4web/server_adapters/adapter_rocket3.py b/py4web/server_adapters/adapter_rocket3.py new file mode 100644 index 000000000..f9d5c7b10 --- /dev/null +++ b/py4web/server_adapters/adapter_rocket3.py @@ -0,0 +1,27 @@ +from ombott.server_adapters import ServerAdapter +from rocket3 import Rocket3 + +from .logging_utils import logging_conf + + +class Rocket3Adapter(ServerAdapter): + "Class implementing a rocket3 server" + + def run(self, handler): + "runs the server" + + if not self.quiet: + logging_conf( + self.options["logging_level"], + ) + + keyfile = self.options.get("keyfile") + certfile = self.options.get("certfile") + + if keyfile and certfile: + interface = (self.host, self.port, keyfile, certfile) + else: + interface = (self.host, self.port) + + server = Rocket3(interface, "wsgi", dict(wsgi_app=handler)) + server.start() diff --git a/py4web/server_adapters/adapter_wsgiref.py b/py4web/server_adapters/adapter_wsgiref.py new file mode 100644 index 000000000..19b7e204a --- /dev/null +++ b/py4web/server_adapters/adapter_wsgiref.py @@ -0,0 +1,109 @@ +"builds and returns a wsgiref threading server" +# https://www.electricmonk.nl/log/2016/02/15/multithreaded-dev-web-server-for-the-python-bottle-web-framework/ + +import socket +import ssl +from concurrent.futures import ThreadPoolExecutor # pip install futures +from socketserver import ThreadingMixIn +from wsgiref.simple_server import WSGIRequestHandler, WSGIServer, make_server + +from ombott.server_adapters import ServerAdapter + +from .logging_utils import logging_conf + + +class WSGIRefAdapter(ServerAdapter): + "Class implementing a WSGIRef server" + + def run(self, handler): + "runs the server" + + self.log = None + + if not self.quiet: + self.log = logging_conf( + self.options["logging_level"], + "wsgiref", + ) + + self_run = self # used in internal classes to access options and logger + + class LogHandler(WSGIRequestHandler): + def address_string(self): # Prevent reverse DNS lookups please. + return self.client_address[0] + + def log_request(self, *args, **kw): + if not self_run.quiet: + WSGIRequestHandler.log_request(self, *args, **kw) + + def log_message(self, formatstr, *args): + if not self_run.quiet: # and ( not args[1] in ['200', '304']) : + msg = "%s - - [%s] %s" % ( + self.client_address[0], + self.log_date_time_string(), + formatstr % args, + ) + self_run.log.info(msg) + + class PoolMixIn(ThreadingMixIn): + def process_request(self, request, client_address): + self.pool.submit(self.process_request_thread, request, client_address) + + class ThreadingWSGIServer(PoolMixIn, WSGIServer): + daemon_threads = True + pool = ThreadPoolExecutor(max_workers=self.options.get("workers", 40)) + + class Server: + def __init__(self, server_address=("127.0.0.1", 8000), handler_cls=None): + self.wsgi_app = None + self.listen, self.port = server_address + self.handler_cls = handler_cls + + def set_app(self, app): + self.wsgi_app = app + + def get_app(self): + return self.wsgi_app + + def serve_forever(self): + self.server = make_server( + self.listen, + self.port, + self.wsgi_app, + ThreadingWSGIServer, + self.handler_cls, + ) + + # openssl req -newkey rsa:4096 -new -x509 -keyout server.pem -out server.pem -days 365 -nodes + # openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 + # ./py4web.py run apps -s wsgirefThreadingServer --watch=off --port=8443 --ssl_cert=cert.pem --ssl_key=key.pem + # openssl s_client -showcerts -connect 127.0.0.1:8443 + + certfile = self_run.options.get("certfile", None) + + if certfile: + self.server.socket = ssl.wrap_socket( + self.server.socket, + certfile=certfile, + keyfile=self_run.options.get("keyfile", None), + ssl_version=ssl.PROTOCOL_SSLv23, + server_side=True, + do_handshake_on_connect=False, + ) + + self.server.serve_forever() + + server_cls = Server + + if ":" in self.host: # Fix wsgiref for IPv6 addresses. + if getattr(server_cls, "address_family") == socket.AF_INET: + + class ServerClass(Server): + address_family = socket.AF_INET6 + + server_cls = ServerClass + + srv = make_server( + self.host, self.port, handler, server_cls, LogHandler + ) # handler_cls) + srv.serve_forever() diff --git a/py4web/server_adapters/logging_utils.py b/py4web/server_adapters/logging_utils.py new file mode 100644 index 000000000..684a2ddd3 --- /dev/null +++ b/py4web/server_adapters/logging_utils.py @@ -0,0 +1,124 @@ +import logging +import os +import sys + + +def make_logger(name, loggers_info): + """ + Abstraction layer on logging. Example of usage: + + from py4web.server_adapters.logging_utils import make_logger + + loggers_info = [ + "warning:warning.log", + "info:info.log", + "debug:debug.log:$(asctime)s > %(levelname)s > %(message)s", + ] + + logger = make_logger("py4web:appname", loggers_info) + """ + default_formatter = "%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s" + # reset loggers + root = logging.getLogger(name) + list(map(root.removeHandler, root.handlers)) + list(map(root.removeFilter, root.filters)) + for logger in loggers_info: + logger += (":stderr" if logger.count(":") == 0 else "") + logger += (":" if logger.count(":") == 1 else "") + level, filename, formatter = logger.split(":", 2) + if not formatter: + formatter = default_formatter + if filename in ("stdout", "stderr"): + handler = logging.StreamHandler(getattr(sys, filename)) + else: + handler = logging.FileHandler(filename) + handler.setFormatter(logging.Formatter(formatter)) + handler.setLevel(getattr(logging, level.upper(), "DEBUG")) + root.addHandler(handler) + return root + + +def get_log_file(out_banner=True): + """ + Returns the filename for logging or None + Assumes: + export PY4WEB_ERRORLOG=/tmp # export PY4WEB_ERRORLOG= + if PY4WEB_ERRORLOG is :stderr or :stdout returns None + if PY4WEB_ERRORLOG is a folder returns the name of a logfile in that dir + if PY4WEB_ERRORLOG is a filename it returns that filename + if the out_banner argument is true, it outputs the filename + """ + log_dir = os.environ.get("PY4WEB_ERRORLOG", None) + if log_dir and not log_dir.startswith(":"): + if os.path.isdir(log_dir): + log_file = os.path.join(log_dir, "server-py4web.log") + else: + log_file = log_dir + if out_banner: + print(f"log_file: {log_file}") + return log_file + return None + + +def check_level(level): + "Check the level is a valid loglevel" + # lib/python3.7/logging/__init__.py + # CRITICAL = 50 + # FATAL = CRITICAL + # ERROR = 40 + # WARNING = 30 + # WARN = WARNING + # INFO = 20 + # DEBUG = 10 + # NOTSET = 0 + + return ( + level + if level + in ( + logging.CRITICAL, + logging.ERROR, + logging.WARN, + logging.INFO, + logging.DEBUG, + logging.NOTSET, + ) + else logging.WARN + ) + + +def logging_conf(level=logging.WARN, logger_name=__name__, fmode="w"): + "Configures logging" + + log_file = get_log_file() + log_to = {} + + if log_file: + if sys.version_info >= (3, 9): + log_to["filename"] = log_file + log_to["filemode"] = fmode + log_to["encoding"] = "utf-8" + else: + h = logging.FileHandler(log_file, mode=fmode, encoding="utf-8") + log_to.update({"handlers": [h]}) + + short_msg = "%(message)s > %(threadName)s > %(asctime)s.%(msecs)03d" + # long_msg = short_msg + " > %(funcName)s > %(filename)s:%(lineno)d > %(levelname)s" + + time_msg = "%H:%M:%S" + # date_time_msg = '%Y-%m-%d %H:%M:%S' + + logging.basicConfig( + format=short_msg, + datefmt=time_msg, + level=check_level(level), + **log_to, + ) + + if logger_name is None: + return None + + log = logging.getLogger("SA:" + logger_name) + log.propagate = True + + return log diff --git a/py4web/utils/auth.py b/py4web/utils/auth.py index dfd36e379..e8b501ebd 100644 --- a/py4web/utils/auth.py +++ b/py4web/utils/auth.py @@ -6,18 +6,10 @@ import random import re import time -import urllib import uuid -from pydal.validators import ( - CRYPT, - IS_EMAIL, - IS_EQUAL_TO, - IS_MATCH, - IS_NOT_EMPTY, - IS_NOT_IN_DB, - IS_STRONG, -) +from pydal.validators import (CRYPT, IS_EMAIL, IS_EQUAL_TO, IS_MATCH, + IS_NOT_EMPTY, IS_NOT_IN_DB, IS_STRONG) from yatl.helpers import DIV, A from py4web import HTTP, URL, Field, action, redirect, request, response @@ -70,15 +62,13 @@ def on_success(self, context): self.auth.on_success(context) def abort_or_redirect(self, page, message=""): - """Return HTTP 403 if 'application/json' in HTTP_ACCEPT - and HTTP_JSON_REDIRECTS flag is not set in the request to 'on'. - Else redirects to page + """ + Return HTTP 403 if 'text/htmp' not in HTTP_ACCEPT + else redirects to specified page """ - if re.search(REGEX_APPJSON, request.headers.get("accept", "")) and ( - request.headers.get("json-redirects", "") != "on" - ): - raise HTTP(403) + if "text/html" not in request.headers.get("accept", ""): + raise HTTP(403, body={"message": message}) redirect_next = request.fullpath if request.query_string: redirect_next = redirect_next + "?{}".format(request.query_string) @@ -97,17 +87,22 @@ def goto_login(self, message=""): # If a plugin can handle requests, redirects to the login url for the login. for plugin in self.auth.plugins.values(): if hasattr(plugin, "handle_request"): - if re.search(REGEX_APPJSON, - request.headers.get("accept", "")) and ( - request.headers.get("json-redirects", "") != "on" + if re.search(REGEX_APPJSON, request.headers.get("accept", "")) and ( + request.headers.get("json-redirects", "") != "on" ): raise HTTP(403) redirect_next = request.fullpath if request.query_string: redirect_next = redirect_next + "?{}".format(request.query_string) redirect( - URL(self.auth.route, "plugin", plugin.name, "login", - vars=dict(next=redirect_next))) + URL( + self.auth.route, + "plugin", + plugin.name, + "login", + vars=dict(next=redirect_next), + ) + ) # Otherwise, uses the normal login. self.abort_or_redirect("login", message=message) @@ -235,17 +230,20 @@ def __init__( password_in_db=True, two_factor_required=None, two_factor_send=None, + two_factor_validate=None, + template_args=None, ): - # configuration parameters self.param = Param( registration_requires_confirmation=registration_requires_confirmation, registration_requires_approval=registration_requires_approval, login_after_registration=False, login_expiration_time=login_expiration_time, # seconds - password_complexity={"entropy": 50} - if password_complexity == "default" - else password_complexity, + password_complexity=( + {"entropy": 50} + if password_complexity == "default" + else password_complexity + ), block_previous_password_num=block_previous_password_num, allowed_actions=allowed_actions or ["all"], use_appname_in_redirects=use_appname_in_redirects, @@ -258,7 +256,10 @@ def __init__( expose_all_models=True, two_factor_required=two_factor_required, two_factor_send=two_factor_send, + two_factor_validate=two_factor_validate, two_factor_tries=3, + auth_enforcer=None, + template_args=template_args or {}, ) # callbacks for forms @@ -415,13 +416,16 @@ def define_tables(self): readable=False, ) ) - db.define_table("auth_user", *(auth_fields + self.extra_auth_user_fields)) + db.define_table( + "auth_user", + *(auth_fields + self.extra_auth_user_fields), + format=lambda u: f"{u.first_name} {u.last_name}") @property def signature(self): """Returns a list of fields for a table signature""" now = lambda: datetime.datetime.utcnow() - user = lambda s=self: s.get_user().get("id") + user = lambda s=self: s.user_id fields = [ Field( "created_on", @@ -472,11 +476,17 @@ def signature(self): @property def user(self): """Use as @action.uses(auth.user)""" - return AuthEnforcer(self) + return ( + self.param.auth_enforcer if self.param.auth_enforcer else AuthEnforcer(self) + ) def condition(self, condition): """Use as @action.uses(auth.condition(lambda user: True))""" - return AuthEnforcer(self, condition) + return ( + self.param.auth_enforcer + if self.param.auth_enforcer + else AuthEnforcer(self, condition) + ) # utilities def get_user(self, safe=True): @@ -486,24 +496,25 @@ def get_user(self, safe=True): If session contains only a user['id'] retrives the other readable user info from auth_user """ - if not self.session.is_valid(): + if not self.session.is_valid() or not self.user_id: return {} - user = self.session.get("user") - if not user or not isinstance(user, dict) or "id" not in user: - return {} - if len(user) == 1 and self.db: - user = self.db.auth_user(user["id"]) + if self.db: + user = self.db.auth_user(self.user_id) if not user: return {} if safe: - user = {f.name: user[f.name] for f in self.db.auth_user if f.readable} + user = { + f.name: user[f.name] + for f in self.db.auth_user + if f.readable or f.name == "id" + } return user @property def is_logged_in(self): if not self.session.is_valid(): return False - return self.session.get("user", {}).get("id", None) != None + return self.session.get("user", {}).get("id", None) is not None @property def user_id(self): @@ -520,6 +531,36 @@ def user_id(self): def current_user(self): return self.get_user() + def start_impersonating(self, impersonated_id, next_url): + """impersonates the new user""" + user = self.session.get("user") + if not user or "id" not in user: + raise RuntimeError("Cannot impersonate if not logged in") + if "impersonator_id" in user: + raise RuntimeError("Cannot impersonate while impersonating") + if impersonated_id == self.user_id: + raise RuntimeError("Cannot impersonate yourself") + self.session.clear() + self.store_user_in_session(impersonated_id) + self.session["user"]["impersonator_id"] = user["id"] + redirect(next_url) + + def is_impersonating(self): + """checks if we are impersonating a user""" + return self.session.get("user", {}).get("impersonator_id", None) is not None + + def stop_impersonating(self, next_url): + """stops impersonating a user, assuming we are impersonating one""" + user = self.session.get("user") + impersonator_id = (user or {}).get("impersonator_id") + if impersonator_id is None: + raise RuntimeError( + "Cannot stop impersonation because not impersonating anybody" + ) + self.session.clear() + self.store_user_in_session(impersonator_id) + redirect(next_url) + def register_plugin(self, plugin): """Registers an Auth plugin, usually from common.py inside apps""" self.plugins[plugin.name] = plugin @@ -539,12 +580,19 @@ def store_user_in_session(self, user_id): def register(self, fields, send=True, next="", validate=True, route=None): """Registers a new user after the user's parameters are entered in the SignUp form""" - if self.use_username: - fields["username"] = fields.get("username", "").lower() + if "username" in fields: + fields["username"] = fields["username"].lower() fields["email"] = fields.get("email", "").lower() def store(fields): if validate: + if self.use_username: + self.db.auth_user.username.required = True + self.db.auth_user.action_token.writable = True + self.db.auth_user.password.writable = True + self.db.auth_user.password.required = True + self.db.auth_user.first_name.required = True + self.db.auth_user.last_name.required = True return self.db.auth_user.validate_and_insert(**fields) return dict(id=self.db.auth_user.insert(**fields)) @@ -595,7 +643,7 @@ def login(self, email, password): # get of create the user (if does not exist) user_info = {} user_info["sso_id"] = plugin.name + ":" + email - if self.use_username or not "@" in email: + if self.use_username or "@" not in email: user_info["username"] = email if "@" in email: user_info["email"] = email @@ -619,11 +667,11 @@ def login(self, email, password): # then check for possible login blockers if not user: error = "invalid_credentials" - elif (user["action_token"] or "").startswith("pending-registration:"): + elif (user.get("action_token") or "").startswith("pending-registration:"): error = "registration_is_pending" - elif user["action_token"] == "account-blocked": + elif user.get("action_token") == "account-blocked": error = "account_is_blocked" - elif user["action_token"] == "pending-approval": + elif user.get("action_token") == "pending-approval": error = "account_needs_to_be_approved" # return the error or the user @@ -641,7 +689,7 @@ def request_reset_password(self, email, send=True, next="", route=None): field = ( db.auth_user.email if "@" in value or not self.use_username - else self.auth_user.username + else db.auth_user.username ) user = db(field == value).select().first() if user and user.action_token != "account-blocked": @@ -813,32 +861,36 @@ def get_or_delete_existing_unverified_account(self, email): def get_or_register_user(self, user): db = self.db - # if the we have an email for the user - if "email" in user: - # return a user if exists and has a verified email - row = self.get_or_delete_existing_unverified_account(user["email"]) - # else retrieve the user from the sso_id - else: + # if we have an sso_id we use it to id the user + if user.get("sso_id"): + keyid = "sso_id" row = ( db(db.auth_user.sso_id == user["sso_id"]).select(limitby=(0, 1)).first() ) - # if we have found a candidate user - if row: - # we expect the email to match if provided - if "email" in user and row.email != user["email"]: - return None - # we can update all the other information provided by the SSO - if any(user[key] != row[key] for key in user if not key == "username"): + # the sso source is always more authoritative so update the record + if row: row.update_record(**user) - user["id"] = row["id"] - # if we do not have a candidate user we need to create one + # pass the full user + user = row.as_dict() + # othrewise we id the user via email + elif user.get("email"): + keyid = "email" + # return a user if exists and has a verified email + row = self.get_or_delete_existing_unverified_account(user["email"]) + # the database is more authoritative + if row: + user.update(**row.as_dict()) else: - # we expect an email to be able to create account - if not "email" in user: - return None - # if we expect a username but not provided, user email as username - if self.use_username and "username" not in user: - user["username"] = user["email"] + return None + # if we do not have a candidate user we create one + if not row: + # if we expect a username but not provided, use keyid as username + if self.use_username: + if "username" not in user: + user["username"] = user[keyid] + # make sure the username is unique + if db(db.auth_user.username == user["username"]).count(): + raise HTTP(401, body=f"Conficting {user['username']} accounts") # create the user user["id"] = db.auth_user.insert(**db.auth_user._filter_fields(user)) return user @@ -927,7 +979,7 @@ def enable_record_versioning( current_record_label=current_record_label, ) - def enable(self, route="auth", uses=(), env=None, spa=False): + def enable(self, route="auth", uses=(), env=None, spa=False, allow_api_routes=True): """Enables Auth, aka generates login/logout/register/etc API pages""" self.route = route = route.rstrip("/") env = env or {} @@ -944,34 +996,35 @@ def enable(self, route="auth", uses=(), env=None, spa=False): # This exposes all API actions as /{app_name}/{route}/api/{name} # and API Models as /{app_name}/{route}/api/{name}?@model=true - - # Exposed Public APIs - exposed_api_routes = [ - dict(api_name=api_name, api_route=f"{route}/api/{api_name}", uses=auth) - for api_name in AuthAPI.public_api - if self.allows(api_name) - ] - - # Exposed Private APIs - exposed_api_routes.extend( - [ - dict( - api_name=api_name, - api_route=f"{route}/api/{api_name}", - uses=auth.user, - ) - for api_name in AuthAPI.private_api + exposed_api_routes = [] + if allow_api_routes: + # Exposed Public APIs + exposed_api_routes = [ + dict(api_name=api_name, api_route=f"{route}/api/{api_name}", uses=auth) + for api_name in AuthAPI.public_api if self.allows(api_name) ] - ) - for item in exposed_api_routes: - api_factory = getattr(AuthAPI, item["api_name"]) + # Exposed Private APIs + exposed_api_routes.extend( + [ + dict( + api_name=api_name, + api_route=f"{route}/api/{api_name}", + uses=auth.user, + ) + for api_name in AuthAPI.private_api + if self.allows(api_name) + ] + ) + + for item in exposed_api_routes: + api_factory = getattr(AuthAPI, item["api_name"]) - @action(item["api_route"], method=methods) - @action.uses(item["uses"], *uses) - def _(auth=auth, api_factory=api_factory): - return api_factory(auth) + @action(item["api_route"], method=methods) + @action.uses(item["uses"], *uses) + def _(auth=auth, api_factory=api_factory): + return api_factory(auth) # This exposes all plugins as /{app_name}/{route}/plugins/{path} for name in self.plugins: @@ -1015,10 +1068,9 @@ def _(path, plugin=self.plugins[name], name=name): for item in exposed_form_routes: form_factory = getattr(self.form_source, item["form_name"]) - + template = Template(f"{route}.html", **self.param.template_args) @action(item["form_route"], method=["GET", "POST"]) - @action.uses(f"{route}.html") - @action.uses(item["uses"], self.flash, *uses) + @action.uses(template, item["uses"], self.flash, *uses) def _( auth=auth, form_factory=form_factory, @@ -1041,7 +1093,7 @@ def api_wrapper(func): def func_wrapper(auth, func=func): data = func(auth) or {} - if not "status" in data and data.get("errors"): + if "status" not in data and data.get("errors"): data.update(status="error", message="validation errors", code=401) elif "errors" in data and not data["errors"]: del data["errors"] @@ -1110,7 +1162,6 @@ def model_request(route): @staticmethod def get_model(defaultAuthFunction): - model = defaultAuthFunction(model=True) for key, value in model.items(): @@ -1127,9 +1178,11 @@ def get_model(defaultAuthFunction): writable=field.writable if field.type != "id" else False, required=field.required, regex=field.regex if hasattr(field, "regex") else None, - default=field.default() - if callable(field.default) - else field.default, + default=( + field.default() + if callable(field.default) + else field.default + ), options=field.options, ) ) @@ -1304,7 +1357,6 @@ def change_password(auth): @staticmethod @api_wrapper def change_email(auth): - payload = request.POST if (request.json is None) else request.json if payload is None: @@ -1402,7 +1454,7 @@ def register(self, model=False): dict( label=self.auth.param.messages["buttons"]["sign-in"], action="login", - href="/auth/api/login", + href=URL(f"{self.auth.route}/api/login"), ) ) @@ -1411,7 +1463,7 @@ def register(self, model=False): dict( label=self.auth.param.messages["buttons"]["lost-password"], action="request_reset_password", - href="/auth/api/request_reset_password", + href=URL(f"{self.auth.route}/api/request_reset_password"), ) ) @@ -1419,7 +1471,7 @@ def register(self, model=False): public=True, hidden=False, fields=fields, - href="/auth/api/register", + href=URL(f"{self.auth.route}/api/register"), submit_label=button_name, additional_buttons=additional_buttons, ) @@ -1461,7 +1513,7 @@ def register(self, model=False): form.param.sidecar.append( A( self.auth.param.messages["buttons"]["sign-in"], - _href="../auth/login", + _href=URL(f"{self.auth.route}/login"), _class=self.auth.param.button_classes["sign-in"], _role="button", ) @@ -1470,7 +1522,7 @@ def register(self, model=False): form.param.sidecar.append( A( self.auth.param.messages["buttons"]["lost-password"], - _href="../auth/request_reset_password", + _href=URL(f"{self.auth.route}/request_reset_password"), _class=self.auth.param.button_classes["lost-password"], _role="button", ) @@ -1486,7 +1538,7 @@ def login_buttons(self): if not hasattr(plugin, "get_login_url"): continue - url = f"/auth/plugin/{name}/login" + url = URL(f"{self.auth.route}/plugin/{name}/login") next_url = prevent_open_redirect(request.query.get("next")) if next_url: @@ -1498,7 +1550,7 @@ def login_buttons(self): combined_div = DIV( *[ - A(item["label"], _href=f"..{item['href']}", _role="button") + A(item["label"], _href=f"{item['href']}", _role="button") for item in top_buttons ] ) @@ -1518,9 +1570,11 @@ def login(self, model=False): fields = [ Field( "email", - label=self.auth.db.auth_user.username.label - if self.auth.use_username - else self.auth.db.auth_user.email.label, + label=( + self.auth.db.auth_user.username.label + if self.auth.use_username + else self.auth.db.auth_user.email.label + ), ), Field( "password", @@ -1538,7 +1592,7 @@ def login(self, model=False): dict( label=self.auth.param.messages["buttons"]["sign-up"], action="register", - href="/auth/api/register", + href=URL(f"{self.auth.route}/api/register"), ) ) @@ -1547,7 +1601,7 @@ def login(self, model=False): dict( label=self.auth.param.messages["buttons"]["lost-password"], action="request_reset_password", - href="/auth/api/request_reset_password", + href=URL(f"{self.auth.route}/api/request_reset_password"), ) ) @@ -1557,7 +1611,7 @@ def login(self, model=False): public=True, hidden=False, fields=fields, - href="/auth/api/login", + href=URL(f"{self.auth.route}/api/login"), submit_label=button_name, additional_buttons=additional_buttons, ) @@ -1592,7 +1646,7 @@ def login(self, model=False): ): self.auth.session["auth.2fa_user"] = user["id"] self.auth.session["auth.2fa_next_url"] = next_url - redirect(URL("auth", "two_factor")) + redirect(URL(f"{self.auth.route}/two_factor")) self.auth.store_user_in_session(user["id"]) self._postprocessing("login", form, user) @@ -1600,7 +1654,7 @@ def login(self, model=False): form.param.sidecar.append( A( self.auth.param.messages["buttons"]["sign-up"], - _href="../auth/register", + _href=URL(f"{self.auth.route}/register"), _class=self.auth.param.button_classes["sign-up"], _role="button", ) @@ -1609,7 +1663,7 @@ def login(self, model=False): form.param.sidecar.append( A( self.auth.param.messages["buttons"]["lost-password"], - _href="../auth/request_reset_password", + _href=URL(f"{self.auth.route}/request_reset_password"), _class=self.auth.param.button_classes["lost-password"], _role="button", ) @@ -1623,8 +1677,7 @@ def _reset_two_factor(self): self.auth.session["auth.2fa_tries_left"] = self.auth.param.two_factor_tries def two_factor(self): - - if self.auth.param.two_factor_send is None: + if (self.auth.param.two_factor_send is None) and (self.auth.param.two_factor_validate is None): raise HTTP(404) user_id = self.auth.session.get("auth.2fa_user") @@ -1635,13 +1688,27 @@ def two_factor(self): user = self.auth.db.auth_user(user_id) code = self.auth.session.get("auth.2fa_code") - if not code: + if (not code) and (not self.auth.param.two_factor_send is None): # generate and send the code code = str(random.randint(100000, 999999)) code = self.auth.param.two_factor_send(user, code) # store code in session self.auth.session["auth.2fa_code"] = code self.auth.session["auth.2fa_tries_left"] = self.auth.param.two_factor_tries + elif self.auth.session.get("auth.2fa_tries_left") is None: + self.auth.session["auth.2fa_tries_left"] = self.auth.param.two_factor_tries + + def two_factor_validate(form): + # external validation outcome + outcome = None + if self.auth.param.two_factor_validate: + outcome = self.auth.param.two_factor_validate(user, form.vars['authentication_code']) + # outcome: + # True: external validation passed + # False: external validation failed + # None: external validation status unknown - check against the generated code + if outcome==False or ((outcome is None) and (form.vars['authentication_code']!=code)): + form.errors['authentication_code'] = self.auth.param.messages["errors"]["two_factor"] form = Form( [ @@ -1649,12 +1716,9 @@ def two_factor(self): "authentication_code", label=self.auth.param.messages["labels"]["two_factor"], required=True, - requires=IS_EQUAL_TO( - code, - error_message=self.auth.param.messages["errors"]["two_factor"], - ), ), ], + validation=two_factor_validate, formstyle=self.auth.param.formstyle, form_name="auth_2fa", keep_values=True, @@ -1664,7 +1728,7 @@ def two_factor(self): if form.accepted: # reset the 2f session self._reset_two_factor() - # store user i session + # store user in session self.auth.store_user_in_session(user["id"]) # login user self._postprocessing("login", form, user) @@ -1679,7 +1743,7 @@ def two_factor(self): self._set_flash( self.auth.param.messages["errors"]["two_factor_max_tries"] ) - redirect(URL("auth", "login", vars=dict(next=next_url))) + redirect(URL(f"{self.auth.route}/login", vars=dict(next=next_url))) return form def request_reset_password(self, model=False): @@ -1701,7 +1765,7 @@ def request_reset_password(self, model=False): dict( label=self.auth.param.messages["buttons"]["sign-in"], action="login", - href="/auth/api/login", + href=URL(f"{self.auth.route}/api/login"), ) ) @@ -1710,7 +1774,7 @@ def request_reset_password(self, model=False): dict( label=self.auth.param.messages["buttons"]["sign-up"], action="register", - href="/auth/api/register", + href=URL(f"{self.auth.route}/api/register"), ) ) @@ -1718,7 +1782,7 @@ def request_reset_password(self, model=False): public=True, hidden=False, fields=fields, - href="/auth/api/request_reset_password", + href=URL(f"{self.auth.route}/api/request_reset_password"), submit_label=button_name, additional_buttons=additional_buttons, ) @@ -1743,7 +1807,7 @@ def request_reset_password(self, model=False): form.param.sidecar.append( A( self.auth.param.messages["buttons"]["sign-in"], - _href="../auth/login", + _href=URL(f"{self.auth.route}/login"), _class=self.auth.param.button_classes["sign-in"], _role="button", ) @@ -1753,7 +1817,7 @@ def request_reset_password(self, model=False): form.param.sidecar.append( A( self.auth.param.messages["buttons"]["sign-up"], - _href="../auth/register", + _href=URL(f"{self.auth.route}/register"), _class=self.auth.param.button_classes["sign-up"], _role="button", ) @@ -1785,7 +1849,7 @@ def reset_password(self, model=False): public=True, hidden=True, fields=fields, - href="/auth/api/reset_password", + href=URL(f"{self.auth.route}/api/reset_password"), submit_label=button_name, ) @@ -1837,7 +1901,7 @@ def change_password(self, model=False): public=False, hidden=False, fields=fields, - href="/auth/api/change_password", + href=URL(f"{self.auth.route}/api/change_password"), submit_label=button_name, ) @@ -1897,7 +1961,7 @@ def profile(self, model=False): public=False, hidden=False, fields=fields, - href="/auth/api/profile", + href=URL(f"{self.auth.route}/api/profile"), submit_label=button_name, deletable=deletable, ) @@ -1915,10 +1979,9 @@ def profile(self, model=False): return form def logout(self, model=False): - if model: return dict( - public=False, hidden=False, noform=True, href="/auth/api/logout" + public=False, hidden=False, noform=True, href=URL(f"{self.auth.route}/api/logout") ) """Process logout""" @@ -1928,10 +1991,9 @@ def logout(self, model=False): return "" def verify_email(self, model=False): - if model: return dict( - public=True, hidden=True, noform=True, href="/auth/api/verify_email" + public=True, hidden=True, noform=True, href=URL(f"{self.auth.route}/api/verify_email") ) """Process token in email verification""" diff --git a/py4web/utils/auth_plugins/__init__.py b/py4web/utils/auth_plugins/__init__.py index b461c8b6d..865b5eaa1 100644 --- a/py4web/utils/auth_plugins/__init__.py +++ b/py4web/utils/auth_plugins/__init__.py @@ -62,22 +62,30 @@ def _handle_callback(self, auth, get_vars): msg = error.get("message", "Unknown error") raise HTTP(code, msg) if auth.db: + print(data) # map returned fields into auth_user fields user = {} - for key, value in self.maps.items(): - value, parts = data, value.split(".") - for part in parts: - value = value[int(part) if part.isdigit() else part] + for key, orig_key in self.maps.items(): + value = data + parts = orig_key.split(".") + try: + for part in parts: + value = value[int(part) if part.isdigit() else part] user[key] = value + except Exception: + continue user["sso_id"] = "%s:%s" % (self.name, user["sso_id"]) - if not "username" in user: + if "username" not in user: user["username"] = user["sso_id"] # store or retrieve the user data = auth.get_or_register_user(user) + user_id = data["id"] else: # WIP Allow login without DB - if not "id" in data: - data["id"] = data.get("username") or data.get("email") + if "id" not in data: + data["id"] = ( + data.get("sso_id") or data.get("username") or data.get("email") + ) user_id = data.get("id") auth.store_user_in_session(user_id) if "_next" in auth.session: diff --git a/py4web/utils/auth_plugins/basic_auth_plugin.py b/py4web/utils/auth_plugins/basic_auth_plugin.py index 95eb84e06..2d1a1b56c 100644 --- a/py4web/utils/auth_plugins/basic_auth_plugin.py +++ b/py4web/utils/auth_plugins/basic_auth_plugin.py @@ -12,7 +12,6 @@ def __init__(self, server="127.0.0.1", table=None): self.server = server def check_credentials(self, username, password): - """ to use basic login with a different server from gluon.contrib.login_methods.basic_auth import basic_auth diff --git a/py4web/utils/auth_plugins/email_auth_plugin.py b/py4web/utils/auth_plugins/email_auth_plugin.py index 8b4ca1c39..932d07f34 100644 --- a/py4web/utils/auth_plugins/email_auth_plugin.py +++ b/py4web/utils/auth_plugins/email_auth_plugin.py @@ -40,11 +40,12 @@ def check_credentials(self, username, password): server.login(email, password) server.quit() return True - except: + except Exception: logging.exception("email_auth() failed") if server: try: server.quit() - except: # server might already close connection after error + except Exception: + # server might already close connection after error pass return False diff --git a/py4web/utils/auth_plugins/ldap_plugin.py b/py4web/utils/auth_plugins/ldap_plugin.py index a6e16ee93..3128c7e83 100644 --- a/py4web/utils/auth_plugins/ldap_plugin.py +++ b/py4web/utils/auth_plugins/ldap_plugin.py @@ -425,7 +425,7 @@ def check_credentials(self, username, password): con.simple_bind_s(user_dn, password) found = True break - except ldap.LDAPError as detail: + except ldap.LDAPError: (exc_type, exc_value) = sys.exc_info()[:2] logger.warning( "ldap_auth: searching %s for %s resulted in %s: %s\n" @@ -463,7 +463,7 @@ def check_credentials(self, username, password): con.simple_bind_s(user_dn, password) found = True break - except ldap.LDAPError as detail: + except ldap.LDAPError: (exc_type, exc_value) = sys.exc_info()[:2] logger.warning( "ldap_auth: searching %s for %s resulted in %s: %s\n" @@ -484,7 +484,7 @@ def check_credentials(self, username, password): )[user_firstname_part] else: store_user_firstname = user_firstname - except KeyError as e: + except KeyError: store_user_firstname = None try: user_lastname = result[user_lastname_attrib][0] @@ -494,11 +494,11 @@ def check_credentials(self, username, password): )[user_lastname_part] else: store_user_lastname = user_lastname - except KeyError as e: + except KeyError: store_user_lastname = None try: store_user_mail = result[user_mail_attrib][0] - except KeyError as e: + except KeyError: store_user_mail = None update_or_insert_values = { "first_name": store_user_firstname, @@ -540,16 +540,16 @@ def check_credentials(self, username, password): con.unbind() return True - except ldap.INVALID_CREDENTIALS as e: + except ldap.INVALID_CREDENTIALS: return False - except ldap.LDAPError as e: + except ldap.LDAPError: import traceback logger.warning("[%s] Error in ldap processing" % str(username)) logger.debug(traceback.format_exc()) print(traceback.format_exc()) return False - except IndexError as ex: # for AD membership test + except IndexError: # for AD membership test import traceback logger.warning("[%s] Ldap result indexing error" % str(username)) @@ -683,7 +683,7 @@ def do_manage_groups(self, con, username, group_mapping={}): .first() .id ) - except AttributeError as e: + except AttributeError: # # There is no user in local db # We create one @@ -692,7 +692,7 @@ def do_manage_groups(self, con, username, group_mapping={}): db_user_id = db.auth_user.insert( username=username, first_name=username ) - except AttributeError as e: + except AttributeError: db_user_id = db.auth_user.insert( email=username, first_name=username ) diff --git a/py4web/utils/auth_plugins/oauth2facebook.py b/py4web/utils/auth_plugins/oauth2facebook.py index e2f7cf56d..a3f3c6e46 100644 --- a/py4web/utils/auth_plugins/oauth2facebook.py +++ b/py4web/utils/auth_plugins/oauth2facebook.py @@ -1,3 +1,5 @@ +import requests + from . import OAuth2 diff --git a/py4web/utils/auth_plugins/oauth2github.py b/py4web/utils/auth_plugins/oauth2github.py index e7e985096..94e3b82ec 100644 --- a/py4web/utils/auth_plugins/oauth2github.py +++ b/py4web/utils/auth_plugins/oauth2github.py @@ -1,7 +1,5 @@ # from https://requests-oauthlib.readthedocs.io/en/latest/examples/github.html -import requests -from py4web import URL from . import OAuth2 diff --git a/py4web/utils/auth_plugins/oauth2google_scoped.py b/py4web/utils/auth_plugins/oauth2google_scoped.py index d67150701..21e720044 100644 --- a/py4web/utils/auth_plugins/oauth2google_scoped.py +++ b/py4web/utils/auth_plugins/oauth2google_scoped.py @@ -7,27 +7,106 @@ import calendar import json +import re import time import uuid -import google_auth_oauthlib.flow import google.oauth2.credentials +import google_auth_oauthlib.flow +from google.auth.exceptions import RefreshError from googleapiclient.discovery import build - -from py4web import request, redirect, URL, HTTP from pydal import Field +from py4web import HTTP, URL, redirect, request, response +from py4web.utils.auth import REGEX_APPJSON, AuthEnforcer + + +class AuthEnforcerGoogleScoped(AuthEnforcer): + """This class catches certain invalid access errors Google generates + when credentials get stale, and forces the user to login again. + Pass it to Auth as param.auth_enfoercer, as in: + auth.param.auth_enforcer = MyAuthEnforcerGoogleScoped(auth, db) + """ + + def __init__(self, auth, db, condition=None, error_page=None): + super().__init__(auth, condition=condition) + self.db = db + self.error_page = error_page + assert ( + error_page is not None + ), "You need to specify an error page; can't use login." + + def on_error(self, context): + if isinstance(context.get("exception"), RefreshError): + del context["exception"] + self._handle_error() + + def _handle_error(self): + # Removes this Google cookie, trying to enforce loggin in again. + response.delete_cookie("G_ENABLED_IDPS") + self.auth.session.clear() + if re.search(REGEX_APPJSON, request.headers.get("accept", "")) and ( + request.headers.get("json-redirects", "") != "on" + ): + raise HTTP(403) + redirect_next = request.fullpath + if request.query_string: + redirect_next = redirect_next + "?{}".format(request.query_string) + self.auth.flash.set("Invalid credentials") + redirect( + URL( + self.error_page, + vars=dict(next=redirect_next), + use_appname=self.auth.param.use_appname_in_redirects, + ) + ) + + def on_request(self, context): + super().on_request(context) + user = self.auth.session.get("user") + user_info = ( + self.db(self.db.auth_credentials.email == user["email"]).select().first() + ) + if not user_info: + self._handle_error() + credentials_dict = json.loads(user_info.credentials) + if not credentials_dict.get("refresh_token"): + print("Missing credentials:", user["email"], credentials_dict) + self._handle_error() + + class OAuth2GoogleScoped(object): """Class that enables google login via oauth2 with additional scopes. - The authorization info is saved so the scopes can be used later on.""" + The authorization info is saved so the scopes can be used later on. + + NOTE: if you use this plugin, it is also recommended that you set: + + auth.param.auth_enforcer = AuthEnforcerGoogleScoped(auth, error_page="credentials_error") + + and that you create a page at URL("credentials_error") to explain the user + that their credentials have expired, and that they must log in again. + + This because sometimes, when one tries to use the credentials, Google + complains that the refresh action fails due to missing credentials. + This can happen if the user, or Google, has revoked credentials. + We need to catch this error, and log out the user, so the user + can decide whether they want to login (and create credentials) again. + + """ # These values are used for the plugin registration. name = "oauth2googlescoped" label = "Google Scoped" callback_url = "auth/plugin/oauth2googlescoped/callback" - def __init__(self, secrets_file=None, scopes=None, db=None, - define_tables=True, delete_credentials_on_logout=True): + def __init__( + self, + secrets_file=None, + scopes=None, + db=None, + define_tables=True, + delete_credentials_on_logout=True, + ): """ Creates an authorization object for Google with Oauth2 and paramters. @@ -57,23 +136,28 @@ def __init__(self, secrets_file=None, scopes=None, db=None, self._secrets_file = secrets_file # Scopes for which we ask authorization scopes = scopes or [] - self._scopes = ["openid", - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/userinfo.profile"] + scopes + self._scopes = [ + "openid", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ] + scopes self._db = db if db and define_tables: self._define_tables() self._delete_credentials_on_logout = delete_credentials_on_logout - def _define_tables(self): - self._db.define_table('auth_credentials', [ - Field('email'), - Field('name'), # First and last names, all together. - Field('profile_pic'), # URL of profile pic. - Field('credentials', 'text') # Credentials for access, stored in Json for generality. - ]) - + self._db.define_table( + "auth_credentials", + [ + Field("email"), + Field("name"), # First and last names, all together. + Field("profile_pic", "text"), # URL of profile pic. + Field( + "credentials", "text" + ), # Credentials for access, stored in Json for generality. + ], + ) def handle_request(self, auth, path, get_vars, post_vars): """Handles the login request or the callback.""" @@ -85,7 +169,7 @@ def handle_request(self, auth, path, get_vars, post_vars): elif path == "logout": # Deletes the credentials, and clears the session. if self._delete_credentials_on_logout: - email = auth.current_user.get('email') if auth.current_user else None + email = auth.current_user.get("email") if auth.current_user else None if email is not None: self._db(self._db.auth_credentials.email == email).delete() self._db.commit() @@ -95,11 +179,11 @@ def handle_request(self, auth, path, get_vars, post_vars): else: raise HTTP(404) - def _get_login_url(self, auth, state=None): # Creates a flow. flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( - self._secrets_file, scopes=self._scopes) + self._secrets_file, scopes=self._scopes + ) # Sets its callback URL. This is the local URL that will be called # once the user gives permission. """Returns the URL to which the user is directed.""" @@ -107,9 +191,10 @@ def _get_login_url(self, auth, state=None): authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. - access_type='offline', + access_type="offline", # Enable incremental authorization. Recommended as a best practice. - include_granted_scopes='true') + include_granted_scopes="true", + ) auth.session["oauth2googlescoped:state"] = state return authorization_url @@ -117,10 +202,11 @@ def _handle_callback(self, auth, get_vars): # Builds a flow again, this time with the state in it. state = auth.session["oauth2googlescoped:state"] flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( - self._secrets_file, scopes=self._scopes, state=state) + self._secrets_file, scopes=self._scopes, state=state + ) flow.redirect_uri = URL(self.callback_url, scheme=True) # Use the authorization server's response to fetch the OAuth 2.0 tokens. - if state and get_vars.get('state', None) != state: + if state and get_vars.get("state", None) != state: raise HTTP(401, "Invalid state") error = get_vars.get("error") if error: @@ -130,9 +216,9 @@ def _handle_callback(self, auth, get_vars): code = error.get("code", 401) msg = error.get("message", "Unknown error") raise HTTP(code, msg) - if not 'code' in get_vars: + if "code" not in get_vars: raise HTTP(401, "Missing code parameter in response.") - code = get_vars.get('code') + code = get_vars.get("code") flow.fetch_token(code=code) # We got the credentials! credentials = flow.credentials @@ -140,7 +226,7 @@ def _handle_callback(self, auth, get_vars): # see https://github.com/googleapis/google-api-python-client/pull/1088/files # and https://github.com/googleapis/google-api-python-client/issues/1071 # and ?? - user_info_service = build('oauth2', 'v2', credentials=credentials) + user_info_service = build("oauth2", "v2", credentials=credentials) user_info = user_info_service.userinfo().get().execute() email = user_info.get("email") if email is None: @@ -148,7 +234,7 @@ def _handle_callback(self, auth, get_vars): # Finally, we store the credentials, so we can re-use them in order # to use the scopes we requested. if self._db: - credentials_json=json.dumps(self.credentials_to_dict(credentials)) + credentials_json = json.dumps(self.credentials_to_dict(credentials)) self._db.auth_credentials.update_or_insert( self._db.auth_credentials.email == email, email=email, @@ -169,7 +255,7 @@ def _handle_callback(self, auth, get_vars): else: # WIP Allow login without DB user = dict(user_info) - if not "id" in user: + if "id" not in user: user["id"] = user.get("username") or user.get("email") # Stores the user in the session. We do it here, so we store # the complete details, and not just the user_id. @@ -184,15 +270,16 @@ def _handle_callback(self, auth, get_vars): next = URL("index") redirect(next) - @staticmethod def credentials_to_dict(credentials): - return {'token': credentials.token, - 'refresh_token': credentials.refresh_token, - 'token_uri': credentials.token_uri, - 'client_id': credentials.client_id, - 'client_secret': credentials.client_secret, - 'scopes': credentials._scopes} + return { + "token": credentials.token, + "refresh_token": credentials.refresh_token, + "token_uri": credentials.token_uri, + "client_id": credentials.client_id, + "client_secret": credentials.client_secret, + "scopes": credentials._scopes, + } @staticmethod def credentials_from_dict(credentials_dict): diff --git a/py4web/utils/auth_plugins/oauth2server.py b/py4web/utils/auth_plugins/oauth2server.py index 3768282ab..316c6a905 100644 --- a/py4web/utils/auth_plugins/oauth2server.py +++ b/py4web/utils/auth_plugins/oauth2server.py @@ -3,7 +3,7 @@ import jwt -from py4web.core import DAL, HTTP, Field, request +from py4web.core import HTTP, Field, request class OAuthServer(object): diff --git a/py4web/utils/auth_plugins/oauth2wpminiorange.py b/py4web/utils/auth_plugins/oauth2wpminiorange.py index 914ba1e83..14369df66 100644 --- a/py4web/utils/auth_plugins/oauth2wpminiorange.py +++ b/py4web/utils/auth_plugins/oauth2wpminiorange.py @@ -44,7 +44,7 @@ class OAuth2WPMiniorange(OAuth2): default_maps = { # "email": "email", "sso_id": "ID", - "username": "username" + "username": "username", # "first_name": "firstname", # "last_name": "lastname", } @@ -111,7 +111,7 @@ def _handle_callback(self, auth, get_vars): self.name, sso_id, ) - if not "username" in user: + if "username" not in user: user["username"] = "%s:%s" % ( self.name, sso_id, @@ -126,7 +126,7 @@ def _handle_callback(self, auth, get_vars): else: # WIP Allow login without DB - if not "id" in data: + if "id" not in data: data["id"] = data.get("username") or data.get("email") user_id = data.get("id") auth.store_user_in_session(user_id) diff --git a/py4web/utils/auth_plugins/pam.py b/py4web/utils/auth_plugins/pam.py index da77d710a..5af9c7112 100644 --- a/py4web/utils/auth_plugins/pam.py +++ b/py4web/utils/auth_plugins/pam.py @@ -14,20 +14,8 @@ __all__ = ["authenticate"] import sys -from ctypes import ( - CDLL, - CFUNCTYPE, - POINTER, - Structure, - byref, - c_char, - c_char_p, - c_int, - c_uint, - c_void_p, - cast, - sizeof, -) +from ctypes import (CDLL, CFUNCTYPE, POINTER, Structure, byref, c_char, + c_char_p, c_int, c_uint, c_void_p, cast, sizeof) from ctypes.util import find_library libpam = CDLL(find_library("pam")) diff --git a/py4web/utils/auth_plugins/saml2_plugin.py b/py4web/utils/auth_plugins/saml2_plugin.py index 88867df27..9552290dc 100644 --- a/py4web/utils/auth_plugins/saml2_plugin.py +++ b/py4web/utils/auth_plugins/saml2_plugin.py @@ -7,14 +7,13 @@ from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT from saml2.client import Saml2Client -from saml2.config import Config as Saml2Config def obj2dict(obj, processed=None): """ converts any object into a dict, recursively """ - processed = processed if not processed is None else set() + processed = processed if processed is not None else set() if obj is None: return None if isinstance(obj, (int, long, str, unicode, float, bool)): @@ -32,8 +31,8 @@ def obj2dict(obj, processed=None): (key, obj2dict(value, processed)) for key, value in obj.items() if not key.startswith("_") - and not type(value) - in ( + and type(value) + not in ( types.FunctionType, types.LambdaType, types.BuiltinFunctionType, @@ -79,7 +78,7 @@ def saml2_handler(session, request, config_filename=None, entityid=None): unquoted_response, binding, session.saml_outstanding_queries ) res["response"] = data if data else {} - except Exception as e: + except Exception: import traceback res["error"] = traceback.format_exc() diff --git a/py4web/utils/auth_plugins/x509_auth_plugin.py b/py4web/utils/auth_plugins/x509_auth_plugin.py index 87e83bee6..0a25a6e6f 100644 --- a/py4web/utils/auth_plugins/x509_auth_plugin.py +++ b/py4web/utils/auth_plugins/x509_auth_plugin.py @@ -13,9 +13,7 @@ from functools import reduce from gluon.globals import current -from gluon.http import HTTP, redirect from gluon.storage import Storage - # requires M2Crypto from M2Crypto import X509 diff --git a/py4web/utils/dbstore.py b/py4web/utils/dbstore.py index 77b8e7bcd..292a9b7e4 100644 --- a/py4web/utils/dbstore.py +++ b/py4web/utils/dbstore.py @@ -6,7 +6,7 @@ def __init__(self, db, name="py4web_session"): self.__prerequisites__ = [db] Field = db.Field self.db = db - if not name in db.tables: + if name not in db.tables: db.define_table( name, Field("rkey", "string"), diff --git a/py4web/utils/downloader.py b/py4web/utils/downloader.py index 2d523a767..8220ba8cc 100644 --- a/py4web/utils/downloader.py +++ b/py4web/utils/downloader.py @@ -1,7 +1,6 @@ import os import re import shutil -import urllib from pydal.exceptions import NotAuthorizedException, NotFoundException from pydal.helpers.regex import REGEX_UPLOAD_PATTERN @@ -11,7 +10,6 @@ def downloader(db, path, filename, download_filename=None): - """ Given a db, and filesystem path, and the filename of an uploaded file, it retrieves the file, checks permission, and returns or stream its. @@ -36,6 +34,7 @@ def download(filename): fieldname = items.group("field") try: field = db[tablename][fieldname] + path = field.uploadfolder or path # Functionality to handle uploadseparate Field declaration. if field.uploadseparate: @@ -48,8 +47,7 @@ def download(filename): (original_name, stream) = field.retrieve(filename, path, nameonly=True) fullpath = os.path.join(path, filename) if not os.path.exists(fullpath) and hasattr(stream, "read"): - with open(fullpath, "wb") as fp: - shutil.copyfile(fp, stream) + return stream.read() except NotAuthorizedException: raise HTTP(403) except NotFoundException: diff --git a/py4web/utils/factories.py b/py4web/utils/factories.py index 4bb1a02c5..9064d79c2 100644 --- a/py4web/utils/factories.py +++ b/py4web/utils/factories.py @@ -1,13 +1,11 @@ import copy import json -import os -from functools import wraps import jwt from yatl.helpers import TAG from py4web import URL, action, request -from py4web.core import Fixture, Session, dumps +from py4web.core import Fixture, Session class Inject(Fixture): diff --git a/py4web/utils/form.py b/py4web/utils/form.py index c58322ba6..483360c72 100644 --- a/py4web/utils/form.py +++ b/py4web/utils/form.py @@ -7,7 +7,6 @@ import jwt from pydal._compat import to_native from pydal.objects import FieldVirtual -from pydal.validators import Validator from yatl.helpers import ( CAT, DIV, @@ -17,10 +16,7 @@ OPTION, SELECT, SPAN, - TABLE, - TD, TEXTAREA, - TR, XML, A, P, @@ -56,7 +52,7 @@ def get_options(validators): options = item.options break if callable(options): - options = options() + options = options(zero=True) return options @@ -69,7 +65,6 @@ def join_classes(*args): class Widget: - """Prototype widget object for all form widgets""" type_map = { @@ -131,7 +126,7 @@ def make(self, field, value, error, title, placeholder=None, readonly=False): _value="ON", _checked=value, _readonly=readonly, - **attrs + **attrs, ) @@ -176,7 +171,7 @@ def make(self, field, value, error, title, placeholder="", readonly=False): value = list(map(str, value if isinstance(value, list) else [value])) field_options = [ - [k, v, (not k is None and k in value)] + [k, v, (k is not None and k in value)] for k, v in get_options(field.requires) ] option_tags = [ @@ -190,7 +185,7 @@ def make(self, field, value, error, title, placeholder="", readonly=False): _name=field.name, _multiple=multiple, _title=title, - _readonly=readonly + _readonly=readonly, ) return control @@ -202,24 +197,21 @@ def make(self, field, value, error, title, placeholder="", readonly=False): field_id = to_id(field) value = list(map(str, value if isinstance(value, list) else [value])) field_options = [ - [k, v, (not k is None and k in value)] + [k, v, (k is not None and k in value)] for k, v in get_options(field.requires) if k != "" ] for k, v, selected in field_options: - _id = "%s%s" % (field_id, k) - control.append( - INPUT( - v, - _id=_id, - _value=k, - _label=v, - _name=field.name, - _type="radio", - _checked=selected, - ) + _id = "%s-%s" % (field_id, k) + inp = INPUT( + _id=_id, + _value=k, + _label=v, + _name=field.name, + _type="radio", + _checked=selected, ) - control.append(LABEL(v, _for=_id)) + control.append(LABEL(inp, " ", v)) return control @@ -335,20 +327,19 @@ def __call__( form_method=form_method, form_action=form_action, form_enctype=form_enctype, - **kwargs + **kwargs, ) - class_label = self.classes["label"] - class_outer = self.classes["outer"] - class_inner = self.classes["inner"] - class_error = self.classes["error"] - class_info = self.classes["info"] + class_label = self.classes.get("label") or None + class_outer = self.classes.get("outer") or None + class_inner = self.classes.get("inner") or None + class_error = self.classes.get("error") or None + class_info = self.classes.get("info") or None all_fields = [x for x in table] if "_virtual_fields" in dir(table): all_fields += table._virtual_fields for field in all_fields: - is_virtual = isinstance(field, FieldVirtual) # only display field if readable or writable @@ -388,11 +379,12 @@ def __call__( field_value = None field_name = field.name - field_type = field.type field_comment = field.comment if field.comment else "" field_label = field.label input_id = to_id(field) - default = field.default() if callable(field.default) else field.default + default = getattr(field, "default", None) + if callable(default): + default = default() value = vars.get(field.name, default) if not is_virtual else None error = errors.get(field.name) @@ -408,7 +400,6 @@ def __call__( if readonly or not field.writable or field.type == "id" or is_virtual: # for boolean readonly we use a readonly checbox if field.type == "boolean": - control = CheckboxWidget().make( field, value, error, title, readonly=True ) @@ -418,7 +409,6 @@ def __call__( field_value = field.f(vars) else: field_value = compat_represent(field, value, vars) - field_type = "represent" control = DIV(field_value) field_disabled = True @@ -481,6 +471,8 @@ def __call__( controls["titles"][field_name] = title controls["placeholders"][field_name] = placeholder + field_type = str(field.type).replace(" ", "-") + # Set the remain json field attributes. field_attributes["_title"] = title field_attributes["_label"] = field_label @@ -488,7 +480,7 @@ def __call__( field_attributes["_id"] = to_id(field) field_attributes["_class"] = field_class field_attributes["_name"] = field.name - field_attributes["_type"] = field.type + field_attributes["_type"] = field_type field_attributes["_placeholder"] = placeholder field_attributes["_error"] = error field_attributes["_disabled"] = field_disabled @@ -538,7 +530,6 @@ def __call__( form.append(INPUT(_name="id", _value=vars["id"], _hidden=True)) if deletable: - deletable_record_attributes = dict() deletable_field_name = "_delete" @@ -567,7 +558,7 @@ def __call__( SPAN( controls["delete"], _class=class_inner, - _stye="vertical-align: middle;", + _style="vertical-align: middle;", ), P( deletable_record_attributes["_label"], @@ -660,7 +651,6 @@ def __call__( } ) - # ################################################################ # Form object (replaced SQLFORM) # ################################################################ @@ -714,8 +704,8 @@ def __init__( lifespan=None, signing_info=None, submit_value="Submit", - show_id=True, - **kwargs + show_id=False, + **kwargs, ): self.param = Param( formstyle=formstyle, @@ -836,23 +826,26 @@ def __init__( self.record and self.record.get(field_name) or None ) self.vars.update(validated_vars) - if validation: - validation(self) if self.record and dbio: self.vars["id"] = self.record.id + if validation: + validation(self) if not self.errors: + """ for file in uploaded_files: - field, value = file - value = field.store( - value.file, value.filename, field.uploadfolder - ) - if value is not None: - validated_vars[field.name] = value + if field.name not in self.vars: + field, value = file + value = field.store( + value.file, value.filename, field.uploadfolder + ) + if value is not None: + self.vars[field.name] = value + """ self.accepted = True - self.vars.update(validated_vars) if dbio: self.update_or_insert(validated_vars) elif dbio: + self.accepted = True self.deleted = True self.record.delete_record() elif self.record: @@ -908,7 +901,7 @@ def _verify_form(self, post_vars): try: jwt.decode(token, key, algorithms=["HS256"]) return True - except: + except Exception: return False def update_or_insert(self, validated_vars): diff --git a/py4web/utils/grid.py b/py4web/utils/grid.py index f5fdbdd8c..feada929d 100644 --- a/py4web/utils/grid.py +++ b/py4web/utils/grid.py @@ -5,9 +5,9 @@ import base64 import copy import datetime +import functools from urllib.parse import urlparse -import ombott from pydal.objects import Expression, Field, FieldVirtual from yatl.helpers import ( CAT, @@ -29,7 +29,7 @@ I, ) -from py4web import HTTP, URL, redirect, request +from py4web import HTTP, URL, redirect, request, safely from py4web.utils.form import Form, FormStyleDefault, join_classes from py4web.utils.param import Param @@ -50,17 +50,7 @@ def safe_int(text, default): return default -def join_styles(items): - return "".join(items) if isinstance(items, (list, tuple)) else " %s" % items - - -def clean_sc(**kwargs): - """returns a clean dict with _class and _style only if value""" - return {key: value for key, value in kwargs.items() if value} - - class GridClassStyle: - """ Default grid style Internal element names match default class name, other classes can be added @@ -116,61 +106,10 @@ class GridClassStyle: "grid-footer-element": "grid-footer-element info", } - styles = { - "grid-wrapper": "", - "grid-header": "display: table; width: 100%;", - "grid-new-button": "margin-top:4px; height:34px; line-height:34px;", - "grid-search": "display: table-cell; float:right;", - "grid-table-wrapper": "overflow-x: auto; width:100%;", - "grid-table": "", - "grid-sorter-icon-up": "", - "grid-sorter-icon-down": "", - "grid-thead": "", - "grid-tr": "", - "grid-th": "text-align:left; white-space: nowrap; vertical-align: top;", - "grid-td": "text-align: left; vertical-align: top;", - "grid-td-buttons": "text-align: left; white-space: nowrap; vertical-align: top;", - "grid-button": "margin-bottom: 0;", - "grid-details-button": "margin-bottom: 0;", - "grid-edit-button": "margin-bottom: 0;", - "grid-delete-button": "margin-bottom: 0;", - "grid-search-button": "height: 34px;", - "grid-clear-button": "height: 34px;", - "grid-footer": "display: table; width:100%;", - "grid-info": "display: table-cell;", - "grid-pagination": "display: table-cell; text-align:right;", - "grid-pagination-button": "min-width: 20px;", - "grid-pagination-button-current": "min-width: 20px; pointer-events:none; opacity: 0.7;", - "grid-cell-type-string": "white-space: nowrap; vertical-align: top; text-align: left; text-overflow: ellipsis; max-width: 200px;", - "grid-cell-type-text": "vertical-align: top; text-align: left; text-overflow: ellipsis; max-width: 200px;", - "grid-cell-type-boolean": "white-space: nowrap; vertical-align: top; text-align: center;", - "grid-cell-type-float": "white-space: nowrap; vertical-align: top; text-align: right;", - "grid-cell-type-decimal": "white-space: nowrap; vertical-align: top; text-align: right;", - "grid-cell-type-int": "white-space: nowrap; vertical-align: top; text-align: right;", - "grid-cell-type-date": "white-space: nowrap; vertical-align: top; text-align: right;", - "grid-cell-type-time": "white-space: nowrap; vertical-align: top; text-align: right;", - "grid-cell-type-datetime": "white-space: nowrap; vertical-align: top; text-align: right;", - "grid-cell-type-upload": "white-space: nowrap; vertical-align: top; text-align: center;", - "grid-cell-type-list": "white-space: nowrap; vertical-align: top; text-align: left;", - # specific for custom form - "grid-search-form": "", - "grid-search-form-table": "", - "grid-search-form-tr": "border-bottom: none;", - "grid-search-form-td": "", - "grid-search-form-input": "", - "grid-search-form-select": "", - "grid-search-boolean": "", - "grid-header-element": "margin-top:4px; height:34px; line-height:34px;", - "grid-footer-element": "margin-top:4px; height:34px; line-height:34px;", - } - @classmethod - def get(cls, element): + def get(cls, element, default=None): """returns a dict with _class and _style for the element name""" - return clean_sc( - _class=cls.classes.get(element), - _style=cls.styles.get(element), - ) + return cls.classes.get(element, element if default is None else default) class GridClassStyleBulma(GridClassStyle): @@ -225,55 +164,6 @@ class GridClassStyleBulma(GridClassStyle): "grid-footer-element": "grid-footer-element button", } - styles = { - "grid-wrapper": "", - "grid-header": "", - "grid-new-button": "", - "grid-search": "", - "grid-table-wrapper": "", - "grid-table": "", - "grid-sorter-icon-up": "", - "grid-sorter-icon-down": "", - "grid-thead": "", - "grid-tr": "", - "grid-th": "text-align: center; text-transform: uppercase; vertical-align: bottom;", - "grid-td": "", - "grid-td-buttons": "", - "grid-button": "", - "grid-details-button": "", - "grid-edit-button": "", - "grid-delete-button": "", - "grid-search-button": "", - "grid-clear-button": "", - "grid-footer": "padding-top: .5em; padding-bottom: 2em;", - "grid-info": "", - "grid-pagination": "", - "grid-pagination-button": "margin-left: .25em;", - "grid-pagination-button-current": "margin-left: .25em;", - "grid-cell-type-string": "vertical-align: top; text-overflow: ellipsis;", - "grid-cell-type-text": "vertical-align: top; text-overflow: ellipsis;", - "grid-cell-type-boolean": "vertical-align: top; text-align: center", - "grid-cell-type-float": "vertical-align: top; text-align: right", - "grid-cell-type-decimal": "vertical-align: top; text-align: right", - "grid-cell-type-int": "vertical-align: top; text-align: center;", - "grid-cell-type-date": "vertical-align: top; text-align: center;", - "grid-cell-type-time": "vertical-align: top; text-align: center;", - "grid-cell-type-datetime": "vertical-align: top; text-align: center;", - "grid-cell-type-upload": "vertical-align: top; text-align: center;", - "grid-cell-type-list": "vertical-align: top; text-align: left;", - "grid-cell-type-id": "", - # specific for custom form - "grid-search-form": "", - "grid-search-form-table": "", - "grid-search-form-tr": "", - "grid-search-form-td": "", - "grid-search-form-input": "", - "grid-search-form-select": "", - "grid-search-boolean": "padding-top: .5rem;", - "grid-header-element": "", - "grid-footer-element": "", - } - class GridClassStyleBootstrap5(GridClassStyle): """The Grid style for Bootstrap 5""" @@ -327,55 +217,6 @@ class GridClassStyleBootstrap5(GridClassStyle): "grid-footer-element": "grid-footer-element btn btn-sm", } - styles = { - "grid-wrapper": "", - "grid-header": "", - "grid-new-button": "", - "grid-search": "", - "grid-table-wrapper": "", - "grid-table": "", - "grid-sorter-icon-up": "", - "grid-sorter-icon-down": "", - "grid-thead": "", - "grid-tr": "", - "grid-th": "text-align: center; text-transform: uppercase; vertical-align: bottom; text-decoration: none;", - "grid-td": "", - "grid-td-buttons": "white-space: nowrap; width: 1%;", - "grid-button": "", - "grid-details-button": "border-radius: 0 !important;", - "grid-edit-button": "border-radius: 0 !important;", - "grid-delete-button": "border-radius: 0 !important;", - "grid-search-button": "border-radius: 0 !important;", - "grid-clear-button": "border-radius: 0 !important;", - "grid-footer": "padding-top: .5em; padding-bottom: 2em;", - "grid-info": "", - "grid-pagination": "", - "grid-pagination-button": "margin-left: .25em; border-radius: 0 !important;", - "grid-pagination-button-current": "margin-left: .25em; border-radius: 0 !important;", - "grid-cell-type-string": "vertical-align: top; text-overflow: ellipsis;", - "grid-cell-type-text": "vertical-align: top; text-overflow: ellipsis;", - "grid-cell-type-boolean": "vertical-align: top; text-align: center", - "grid-cell-type-float": "vertical-align: top; text-align: right", - "grid-cell-type-decimal": "vertical-align: top; text-align: right", - "grid-cell-type-int": "vertical-align: top; text-align: center;", - "grid-cell-type-date": "vertical-align: top; text-align: center;", - "grid-cell-type-time": "vertical-align: top; text-align: center;", - "grid-cell-type-datetime": "vertical-align: top; text-align: center;", - "grid-cell-type-upload": "vertical-align: top; text-align: center;", - "grid-cell-type-list": "vertical-align: top; text-align: left;", - "grid-cell-type-id": "", - # specific for custom form - "grid-search-form": "", - "grid-search-form-table": "", - "grid-search-form-tr": "", - "grid-search-form-td": "", - "grid-search-form-input": "", - "grid-search-form-select": "", - "grid-search-boolean": "padding-top: .5rem;", - "grid-header-element": "", - "grid-footer-element": "", - } - class Column: """class used to represent a column in a grid""" @@ -384,73 +225,69 @@ def __init__( self, name, represent, - required_fields=None, + key=None, + required_fields=None, # must be a list or none orderby=None, + col_type="string", td_class_style=None, ): self.name = name self.represent = represent self.orderby = orderby - self.required_fields = [] - if required_fields: - if isinstance(required_fields, list): - self.required_fields = required_fields - else: - self.required_fields = [required_fields] - + self.required_fields = required_fields or [] + self.key = key + self.type = (col_type,) self.td_class_style = td_class_style - def render(self, row, index=None): - """renders a row al position index (optional)""" - return self.represent(row) - class Grid: - FORMATTERS_BY_TYPE = { - "boolean": lambda value: INPUT( - _type="checkbox", _checked=value, _disabled="disabled" - ) - if value - else "", - "datetime": lambda value: XML( - "" - % ( - value.year, - value.month - 1, - value.day, - value.hour, - value.minute, - value.second, + "NoneType": lambda value: "", + "bool": lambda value: "☑" if value else "☐" if value is False else "", + "float": lambda value: "%.2f" % value, + "double": lambda value: "%.2f" % value, + "datetime": lambda value: ( + XML( + "" + % ( + value.year, + value.month - 1, + value.day, + value.hour, + value.minute, + value.second, + ) ) - ) - if value and isinstance(value, datetime.datetime) - else value - if value - else "", - "time": lambda value: XML( - "" - % (value.hour, value.minute, value.second) - ) - if value and isinstance(value, datetime.time) - else value - if value - else "", - "date": lambda value: XML( - '' - % ( - value.year, - value.month - 1, - value.day, + if value and isinstance(value, datetime.datetime) + else value + if value + else "" + ), + "time": lambda value: ( + XML( + "" + % (value.hour, value.minute, value.second) ) - ) - if value and isinstance(value, datetime.date) - else value - if value - else "", - "list:string": lambda value: ", ".join(str(x) for x in value) if value else "", - "list:integer": lambda value: ", ".join(x for x in value) if value else "", - "default": lambda value: str(value) if value is not None else "", + if value and isinstance(value, datetime.time) + else value + if value + else "" + ), + "date": lambda value: ( + XML( + '' + % ( + value.year, + value.month - 1, + value.day, + ) + ) + if value and isinstance(value, datetime.date) + else value + if value + else "" + ), + "list": lambda value: ", ".join(x for x in value) if value else "", } def __init__( @@ -470,6 +307,7 @@ def __init__( editable=True, deletable=True, validation=None, + required_fields=None, pre_action_buttons=None, post_action_buttons=None, auto_process=True, @@ -482,6 +320,7 @@ def __init__( groupby=None, # deprecated fields=None, + form_maker=Form, ): """ Grid is a searchable/sortable/pageable grid @@ -521,9 +360,13 @@ def __init__( if isinstance(query, query._db.Table): query = query._id != None + if fields and any(field.type == "id" for field in fields): + show_id = True + self.path = path self.db = query._db self.T = T + self.form_maker = form_maker self.param = Param( query=query, columns=columns or fields, @@ -559,6 +402,7 @@ def __init__( delete_action_button_text="Delete", header_elements=None, footer_elements=None, + required_fields=required_fields or [], ) # instance variables that will be computed @@ -570,13 +414,11 @@ def __init__( self.number_of_pages = None self.page_end = None self.page_start = None - self.query_parms = request.params + self.query_parms = safely(lambda: request.params, default={}) self.record_id = None self.rows = None self.tablename = None self.total_number_of_rows = None - self.use_tablename = self.is_join() - self.formatters = {} self.formatters_by_type = copy.copy(Grid.FORMATTERS_BY_TYPE) self.attributes_plugin = AttributesPlugin(request) @@ -625,7 +467,7 @@ def process(self): query_lambda = self.param.search_queries[search_type][1] try: query = query_lambda(search_string) - except: + except Exception: pass # flash a message here if not query: @@ -645,52 +487,82 @@ def process(self): self.record_id = safe_int(parts[1] if len(parts) > 1 else None, default=None) table = db[self.tablename] + # if no column specified use all fields if not self.param.columns: - # if no column specified use all fields self.param.columns = [field for field in table if field.readable] - - # if any columns are Expression but not Field, get the field info from 'first' attribute - converted_columns = [] - for col in self.param.columns: - if isinstance(col, Expression) and not isinstance(col, Field): - converted_columns.append(col.first) + # convert to column object + self.columns = [] + + def title(col): + return str(col).replace('"', "") + + def col2key(col): + return str(col).lower().replace(".", "-") + + for index, col in enumerate(self.param.columns): + if isinstance(col, Column): + if not col.key: + col.key = f"column-{index}" + self.columns.append(col) + + elif isinstance(col, Field): + + def compute(row, col=col): + value = row(str(col)) + if col.represent: + value = col.represent(value) + # deal with download links in special manner if no representation + if col.type == "upload" and value and col.download_url: + value = A("download", _href=col.download_url(value)) + return value + + self.columns.append( + Column( + col.label, + compute, + orderby=col, + required_fields=[col], + key=col2key(col), + col_type=col.type, + ) + ) + elif isinstance(col, FieldVirtual): + + def compute(row, col=col): + return col.f(row) if "id" in row else col.f(row[col.tablename]) + + self.columns.append( + Column( + col.label, + compute, + orderby=None, + required_fields=db[col.tablename], + key=col2key(col), + ) + ) + elif isinstance(col, Expression): + + def compute(row, name=str(col)): + return row._extra(name) + + self.columns.append( + Column( + title(col), + compute, + orderby=None, + required_fields=[col], + key=f"column-{index}", + ) + ) else: - converted_columns.append(col) - self.param.columns = converted_columns - - if not self.param.columns: - self.needed_fields = self.param.columns[:] - elif any(isinstance(col, Column) for col in self.param.columns): - # if we use columns we have to get all fields and assume a single table - self.needed_fields = [field for field in db[self.tablename]] - for col in self.param.columns: - if isinstance(col, Column): - for rf in col.required_fields: - if rf.longname not in [x.longname for x in self.needed_fields]: - self.needed_fields.append(rf) - elif any(isinstance(col, FieldVirtual) for col in self.param.columns): - # if virtual fields are specified the fields may come from a join - needed_fields = set() - for col in self.param.columns: - if isinstance(col, Field): - needed_fields.add(col) - elif isinstance(col, FieldVirtual): - for field in db[col.tablename]: - needed_fields.add(field) - self.needed_fields = list(needed_fields) - else: - self.needed_fields = self.param.columns[:] - - # make sure all specified fields are available - if self.param.columns: - for col in self.param.columns: - if not isinstance(col, (Column, FieldVirtual)): - if col.longname not in [x.longname for x in self.needed_fields]: - self.needed_fields.append(col) + raise RuntimeError(f"Column not support {col}") - # except the primary key may be missing and must be fetched even if not displayed - if not any(col.name == table._id.name for col in self.needed_fields): - self.needed_fields.insert(0, table._id) + # join the set of all required fields + sets = [set(self.param.required_fields or [])] + sets += [set(col.required_fields) for col in self.columns] + self.needed_fields = list( + functools.reduce(lambda a, b: a | b, sets) | set([table._id]) + ) self.referrer = None @@ -729,11 +601,10 @@ def process(self): ) if self.action in ["new", "details", "edit"]: - readonly = self.action == "details" attrs = self.attributes_plugin.form(url=request.url.split(":", 1)[1]) - self.form = Form( + self.form = self.form_maker( table, record=record, readonly=readonly, @@ -760,11 +631,7 @@ def process(self): self.form.param.submit_value = self.param.edit_submit_value # redirect to the referrer - if ( - self.form.accepted - or (readonly and request.method == "POST") - or (self.form.deletable and self.form.deleted) - ): + if self.form.accepted or (readonly and request.method == "POST"): referrer = request.query.get("_referrer") if referrer: redirect(base64.b16decode(referrer.encode("utf8")).decode("utf8")) @@ -785,35 +652,24 @@ def process(self): request.url.encode("utf8") ).decode("utf8") - # find the primary key of the primary table - pt = db[self.tablename] - key_is_missing = True - for field in self.param.columns: - if ( - isinstance(field, Field) - and field.table._tablename == pt._tablename - and field.name == pt._id.name - ): - key_is_missing = False - if key_is_missing: - # primary key wasn't included, add it and set show_id to False so it doesn't display - self.param.columns.append(pt._id) - self.param.show_id = False - self.current_page_number = safe_int(request.query.get("page"), default=1) select_params = dict() # try getting sort order from the request - sort_order = request.query.get("orderby", "") + sort_order = request.query.get("orderby") - try: + select_params["orderby"] = self.param.orderby + if sort_order: parts = sort_order.lstrip("~").split(".") - orderby = db[parts[0]][parts[1]] - if sort_order.startswith("~"): - orderby = ~orderby - select_params["orderby"] = orderby - except (IndexError, KeyError, TypeError, AttributeError): - select_params["orderby"] = self.param.orderby + if ( + len(parts) == 2 + and parts[0] in db.tables + and parts[1] in db[parts[0]] + ): + orderby = db[parts[0]][parts[1]] + if sort_order.startswith("~"): + orderby = ~orderby + select_params["orderby"] = orderby if self.param.left: select_params["left"] = self.param.left @@ -821,13 +677,8 @@ def process(self): if self.param.groupby: select_params["groupby"] = self.param.groupby - if self.param.groupby: + if self.param.groupby or self.param.left: # need groupby fields in select to get proper count - self.total_number_of_rows = len( - db(query).select(*self.param.groupby, **select_params) - ) - elif self.param.left: - # TODO: maybe this can be made more efficient self.total_number_of_rows = len( db(query).select(db[self.tablename].id, **select_params) ) @@ -866,8 +717,14 @@ def process(self): or self.param.deletable or self.param.post_action_buttons ): - self.param.columns.append( - Column("", self.make_action_buttons, td_class_style="grid-td-buttons") + key = f"column-{len(self.columns)}" + self.columns.append( + Column( + "", + self.make_action_buttons, + key=key, + td_class_style=self.param.grid_class_style.get("grid-td-buttons"), + ) ) def iter_pages( @@ -904,9 +761,7 @@ def _make_action_button( icon, icon_size="small", # deprecated additional_classes=None, - additional_styles=None, override_classes=None, - override_styles=None, message=None, onclick=None, # deprecated row_id=None, @@ -918,29 +773,18 @@ def _make_action_button( if row_id: url += "/%s" % row_id - classes = self.param.grid_class_style.classes.get(name, "") - styles = self.param.grid_class_style.styles.get(name, "") + classes = self.param.grid_class_style.get(name) if callable(additional_classes): additional_classes = additional_classes(row) - if callable(additional_styles): - additional_styles = additional_styles(row) - if callable(override_classes): override_classes = override_classes(row) - if callable(override_styles): - override_styles = override_styles(row) - if override_classes: classes = join_classes(override_classes) elif additional_classes: classes = join_classes(classes, additional_classes) - if override_styles: - styles = join_styles(override_styles) - elif additional_styles: - styles += join_styles(additional_styles) if callable(url): url = url(row) @@ -955,7 +799,7 @@ def _make_action_button( _role="button", _message=message, _title=button_text, - **clean_sc(_class=classes, _style=styles), + _class=classes, **attrs, ) if self.param.include_action_button_text: @@ -965,6 +809,12 @@ def _make_action_button( return link + def reformat(self, value): + type_name = type(value).__name__ + if type_name in self.formatters_by_type: + return self.formatters_by_type[type_name](value) + return value + def _make_default_form(self): search_type = safe_int(request.query.get("search_type", 0), default=0) search_string = request.query.get("search_string") @@ -975,55 +825,55 @@ def _make_default_form(self): hidden_fields = [ INPUT(_name=key, _value=request.query.get(key), _type="hidden") for key in request.query - if not key in ("search_type", "search_string") + if key not in ("search_type", "search_string") ] attrs = self.attributes_plugin.link(url=self.endpoint) form = FORM(*hidden_fields, **attrs) - sc = self.param.grid_class_style.get("grid-search-form-select") - select = SELECT( - *options, - **dict( - _name="search_type", - ), - **sc, - ) - sc = self.param.grid_class_style.get("grid-search-form-input") + classes = self.param.grid_class_style.get("grid-search-form-select") + select = SELECT(*options, **dict(_name="search_type", _class=classes)) + classes = self.param.grid_class_style.get("grid-search-form-input") input = INPUT( _type="text", _name="search_string", _value=search_string, - **sc, + _class=classes, ) - sc = self.param.grid_class_style.get("grid-search-button") - submit = INPUT(_type="submit", _value=self.T("Search"), **sc) + classes = self.param.grid_class_style.get("grid-search-button") + submit = INPUT(_type="submit", _value=self.T("Search"), _class=classes) clear_script = "document.querySelector('[name=search_string]').value='';" - sc = self.param.grid_class_style.get("grid-clear-button") + classes = self.param.grid_class_style.get("grid-clear-button") clear = INPUT( - _type="submit", _value=self.T("Clear"), _onclick=clear_script, **sc + _type="submit", + _value=self.T("Clear"), + _onclick=clear_script, + _class=classes, + ) + div = DIV( + _id="grid-search", _classes=self.param.grid_class_style.get("grid-search") ) - div = DIV(_id="grid-search", **self.param.grid_class_style.get("grid-search")) - sc = self.param.grid_class_style.get("grid-search-form-tr") - tr = TR(**sc) - sc = self.param.grid_class_style.get("grid-search-form-td") + tr = TR(_class=self.param.grid_class_style.get("grid-search-form-tr")) + classes = self.param.grid_class_style.get("grid-search-form-td") if len(options) > 1: - tr.append(TD(select, **sc)) - tr.append(TD(input, **sc)) - tr.append(TD(submit, clear, **sc)) - sc = self.param.grid_class_style.get("grid-search-form-table") - form.append(TABLE(tr, **sc)) + tr.append(TD(select, _class=classes)) + tr.append(TD(input, _class=classes)) + tr.append(TD(submit, clear, _class=classes)) + classes = self.param.grid_class_style.get("grid-search-form-table") + form.append(TABLE(tr, _class=classes)) div.append(form) return div def _make_search_form(self): # TODO: Do we need this? - div = DIV(_id="grid-search", **self.param.grid_class_style.get("grid-search")) + div = DIV( + _id="grid-search", _class=self.param.grid_class_style.get("grid-search") + ) div.append(self.param.search_form.custom["begin"]) - tr = TR(**self.param.grid_class_style.get("grid-search-form-tr")) + tr = TR(_class=self.param.grid_class_style.get("grid-search-form-tr")) for field in self.param.search_form.table: - td = TD(**self.param.grid_class_style.get("grid-search-form-td")) + td = TD(_class=self.param.grid_class_style.get("grid-search-form-td")) if field.type == "boolean": - sb = DIV(**self.param.grid_class_style.get("grid-search-boolean")) + sb = DIV(_class=self.param.grid_class_style.get("grid-search-boolean")) sb.append(self.param.search_form.custom["widgets"][field.name]) sb.append(field.label) td.append(sb) @@ -1033,12 +883,7 @@ def _make_search_form(self): field.name in self.param.search_form.custom["errors"] and self.param.search_form.custom["errors"][field.name] ): - td.append( - DIV( - self.param.search_form.custom["errors"][field.name], - _style="color:#ff0000", - ) - ) + td.append(DIV(self.param.search_form.custom["errors"][field.name])) tr.append(td) if self.param.search_button_text: tr.append( @@ -1048,21 +893,24 @@ def _make_search_form(self): _type="submit", _value=self.T(self.param.search_button_text), ), - **self.param.grid_class_style.get("grid-search-form-td"), + _class=self.param.grid_class_style.get("grid-search-form-td"), ) ) else: tr.append( TD( self.param.search_form.custom["submit"], - **self.param.grid_class_style.get("grid-search-form-td"), + _class=self.param.grid_class_style.get("grid-search-form-td"), ) ) div.append( - TABLE(tr, **self.param.grid_class_style.get("grid-search-form-table")) + TABLE(tr, _class=self.param.grid_class_style.get("grid-search-form-table")) ) for hidden_widget in self.param.search_form.custom["hidden_widgets"].keys(): - div.append(self.param.search_form.custom["hidden_widgets"][hidden_widget]) + if hidden_widget not in ("formname", "formkey"): + div.append( + self.param.search_form.custom["hidden_widgets"][hidden_widget] + ) div.append(self.param.search_form.custom["end"]) @@ -1071,173 +919,74 @@ def _make_search_form(self): def _make_table_header(self): sort_order = request.query.get("orderby", "") - thead = THEAD( - **clean_sc(_class=self.param.grid_class_style.classes.get("grid-thead", "")) - ) - for index, column in enumerate(self.param.columns): - col = None - if isinstance(column, (Field, FieldVirtual)): - field = column - if field.readable and (field.type != "id" or self.param.show_id): - key, col = self._make_field_header(column, index, sort_order) - elif isinstance(column, Column): - key = column.name.lower().replace(" ", "-") - col = column.name - if column.orderby: - key, col = self._make_field_header(column, index, sort_order) - else: - raise RuntimeError("Invalid Grid Column type") - if col is not None: - classes = join_classes( - self.param.grid_class_style.classes.get("grid-th"), - "grid-col-%s" % key, - ) - style = self.param.grid_class_style.styles.get("grid-th") - thead.append(TH(col, **clean_sc(_class=classes, _style=style))) + thead = THEAD(_class=self.param.grid_class_style.get("grid-thead")) + for index, col in enumerate(self.columns): + col_header = self._make_col_header(col, index, sort_order) + classes = join_classes( + self.param.grid_class_style.get("grid-th"), + "grid-col-%s" % col.key, + ) + thead.append(TH(col_header, _class=classes)) return thead - def _make_field_header(self, field, field_index, sort_order): - up = I(**self.param.grid_class_style.get("grid-sorter-icon-up")) - dw = I(**self.param.grid_class_style.get("grid-sorter-icon-down")) + def _make_col_header(self, col, index, sort_order): + up = I(_class=self.param.grid_class_style.get("grid-sorter-icon-up")) + dw = I(_class=self.param.grid_class_style.get("grid-sorter-icon-down")) - if isinstance(field, Column): - key = str(field.orderby) - else: - key = "%s.%s" % (field.tablename, field.name) + orderby = col.orderby and str(col.orderby) heading = ( - self.param.headings[field_index] - if field_index < len(self.param.headings) - else getattr(field, "label", title(field.name)) + self.param.headings[index] if index < len(self.param.headings) else col.name ) # add the sort order query parm sort_query_parms = dict(self.query_parms) attrs = {} - if isinstance(field, FieldVirtual): - col = SPAN(heading) - elif key == sort_order: - sort_query_parms["orderby"] = "~" + key - url = URL(self.endpoint, "select", vars=sort_query_parms) - attrs = self.attributes_plugin.link(url=url) - col = A(heading, up, **attrs) - else: - sort_query_parms["orderby"] = key - url = URL(self.endpoint, "select", vars=sort_query_parms) - attrs = self.attributes_plugin.link(url=url) - col = A(heading, dw if "~" + key == sort_order else "", **attrs) - return key, col - - def _make_field(self, row, field, field_index): - """ - Render a field - - if only 1 table in the query, the no table name needed when getting the row value - however, if there - are multiple tables in the query (self.use_tablename == True) then we need to use the tablename as well - when accessing the value in the row object - - the row object sent in can take - :param row: - :param field: - :return: - """ - if self.use_tablename: - row = row[field.tablename] - if isinstance(field, FieldVirtual): - field_value = field.f(row) - else: - field_value = row[field.name] - key = "%s.%s" % (field.tablename, field.name) - # custom formatter overwrites represent - if key in self.formatters: - formatter = self.formatters.get(key) - # else if represent provided use it - elif field.represent: - formatter = field.represent - # else fallback on the formatter for the type - elif field.type in self.formatters_by_type: - formatter = self.formatters_by_type.get(field.type) - # else fallback to default + if orderby: + if orderby == sort_order: + sort_query_parms["orderby"] = "~" + orderby + url = URL(self.endpoint, "select", vars=sort_query_parms) + attrs = self.attributes_plugin.link(url=url) + col_header = A(heading, up, **attrs) + else: + sort_query_parms["orderby"] = orderby + url = URL(self.endpoint, "select", vars=sort_query_parms) + attrs = self.attributes_plugin.link(url=url) + col_header = A( + heading, dw if "~" + orderby == sort_order else "", **attrs + ) else: - formatter = self.formatters_by_type.get("default") - # allow 1 (value) or 2 (value + row) args - try: - formatted_value = formatter(field_value, row) - except TypeError: - formatted_value = formatter(field_value) - - class_type = "grid-cell-type-%s" % str(field.type).split(":")[0].split("(")[0] - class_col = " grid-col-%s" % key.replace(".", "_") - classes = join_classes( - self.param.grid_class_style.classes.get("grid-td"), - self.param.grid_class_style.classes.get(class_type), - class_col, - ) - td = TD( - formatted_value, - **clean_sc( - _class=classes, - _style=( - self.param.grid_class_style.styles.get(class_type) - or self.param.grid_class_style.styles.get("grid-td") - ), - ), - ) - - return td + col_header = heading + return col_header def _make_table_body(self): tbody = TBODY() - for row in self.rows: + for index, row in enumerate(self.rows): # find the row id - there may be nested tables.... - if self.use_tablename and self.tablename in row and "id" not in row: - row_id = row[self.tablename]["id"] - else: - row_id = row["id"] - self.use_tablename = False - key = "%s.%s" % (self.tablename, "__row") - if self.formatters.get(key): - extra_class = self.formatters.get(key)(row)["_class"] - extra_style = self.formatters.get(key)(row)["_style"] - else: - extra_class = "" - extra_style = "" tr = TR( _role="row", - **clean_sc( - _class=join_classes( - self.param.grid_class_style.classes.get("grid-tr"), extra_class - ), - _style=join_styles( - [self.param.grid_class_style.styles.get("grid-tr"), extra_style] - ), - ), + _class=self.param.grid_class_style.get("grid-tr"), ) + # add all the fields to the row - for index, column in enumerate(self.param.columns): - if isinstance(column, (Field, FieldVirtual)): - field = column - if field.readable and (field.type != "id" or self.param.show_id): - tr.append(self._make_field(row, field, index)) - elif isinstance(column, Column): - classes = self.param.grid_class_style.classes.get( - column.td_class_style, - self.param.grid_class_style.classes.get("grid-td"), - ) - style = self.param.grid_class_style.styles.get( - column.td_class_style, - self.param.grid_class_style.styles.get("grid-td"), - ) - tr.append( - TD( - column.render(row, index), - **clean_sc(_class=classes, _style=style), - ) - ) + for col in self.columns: + classes = join_classes( + [ + self.param.grid_class_style.get( + col.td_class_style, + col.td_class_style(row) + if callable(col.td_class_style) + else self.param.grid_class_style.get("grid-td"), + ), + f"grid-cell-{col.key}", + ] + ) + value = col.represent(row) + reformatted_value = self.reformat(value) + tr.append(TD(reformatted_value, _class=classes)) - td = None tbody.append(tr) return tbody @@ -1251,7 +1000,7 @@ def make_action_buttons(self, row): # a button can be a callable, to indicate whether or not a button should # be displayed. call the function with the row object btn = btn(row) - if btn == None: + if btn is None: # if None was returned, no button is available for this row: ignore this value in the # list continue @@ -1267,16 +1016,16 @@ def make_action_buttons(self, row): button_text=self.T(btn.text), icon=btn.icon, additional_classes=btn.additional_classes, - additional_styles=btn.__dict__.get("additional_styles"), override_classes=btn.__dict__.get("override_classes"), - override_styles=btn.__dict__.get("override_styles"), message=btn.message, row_id=row_id if btn.append_id else None, name=btn.__dict__.get("name"), row=row, - ignore_attribute_plugin=btn.ignore_attribute_plugin - if "ignore_attribute_plugin" in btn.__dict__ - else False, + ignore_attribute_plugin=( + btn.ignore_attribute_plugin + if "ignore_attribute_plugin" in btn.__dict__ + else False + ), **attrs, ) ) @@ -1338,7 +1087,7 @@ def make_action_buttons(self, row): # a button can be a callable, to indicate whether or not a button should # be displayed. call the function with the row object btn = btn(row) - if btn == None: + if btn is None: # if None was returned, no button is available for this row: ignore this value in the # list continue @@ -1353,16 +1102,16 @@ def make_action_buttons(self, row): button_text=self.T(btn.text), icon=btn.icon, additional_classes=btn.additional_classes, - additional_styles=btn.__dict__.get("override_styles"), override_classes=btn.__dict__.get("override_classes"), - override_styles=btn.__dict__.get("override_styles"), message=btn.message, row_id=row_id if btn.append_id else None, name=btn.__dict__.get("name"), row=row, - ignore_attribute_plugin=btn.ignore_attribute_plugin - if "ignore_attribute_plugin" in btn.__dict__ - else False, + ignore_attribute_plugin=( + btn.ignore_attribute_plugin + if "ignore_attribute_plugin" in btn.__dict__ + else False + ), **attrs, ) ) @@ -1370,7 +1119,7 @@ def make_action_buttons(self, row): return cat def _make_table_pager(self): - pager = DIV(**self.param.grid_class_style.get("grid-pagination")) + pager = DIV(_class=self.param.grid_class_style.get("grid-pagination")) previous_page_number = None for page_number in self.iter_pages( self.current_page_number, self.number_of_pages @@ -1379,7 +1128,7 @@ def _make_table_pager(self): pager_query_parms["page"] = page_number # if there is a gat add a spacer if previous_page_number and page_number - previous_page_number > 1: - pager.append(SPAN("...", _style="margin:0 10px;")) + pager.append(SPAN("...")) is_current = self.current_page_number == page_number page_name = ( "grid-pagination-button-current" @@ -1392,7 +1141,7 @@ def _make_table_pager(self): pager.append( A( page_number, - **self.param.grid_class_style.get(page_name), + _class=self.param.grid_class_style.get(page_name), _role="button", **attrs, ) @@ -1401,9 +1150,8 @@ def _make_table_pager(self): return pager def _make_table(self): - - html = DIV(**self.param.grid_class_style.get("grid-wrapper")) - grid_header = DIV(**self.param.grid_class_style.get("grid-header")) + html = DIV(_class=self.param.grid_class_style.get("grid-wrapper")) + grid_header = DIV(_class=self.param.grid_class_style.get("grid-header")) # build the New button if needed if self.param.create and self.param.create != "": @@ -1420,12 +1168,7 @@ def _make_table(self): self.T(self.param.new_action_button_text), "fa-plus", icon_size="normal", - override_classes=self.param.grid_class_style.classes.get( - "grid-new-button", "" - ), - override_styles=self.param.grid_class_style.styles.get( - "grid-new-button" - ), + override_classes=self.param.grid_class_style.get("grid-new-button"), ) ) if self.param.header_elements and len(self.param.header_elements) > 0: @@ -1437,19 +1180,9 @@ def _make_table(self): else: override_classes = element.__dict__.get("override_classes", None) if not override_classes: - override_classes = ( - self.param.grid_class_style.classes.get( - "grid-header-element", "" - ) - + f" {element.additional_classes}" - ) - override_styles = element.__dict__.get("override_styles", None) - if not override_styles: - override_styles = ( - self.param.grid_class_style.styles.get( - "grid-trailer-element", "" - ) - + f" {element.__dict__.get('additional_styles')}" + override_classes = join_classes( + self.param.grid_class_style.get("grid-header-element"), + element.additional_classes, ) grid_header.append( self._make_action_button( @@ -1458,9 +1191,7 @@ def _make_table(self): icon=element.icon, icon_size="normal", additional_classes=element.additional_classes, - additional_styles=element.__dict__.get("additional_styles"), override_classes=override_classes, - override_styles=override_styles, message=element.message, name=element.__dict__.get("name"), ignore_attribute_plugin=element.ignore_attribute_plugin, @@ -1476,7 +1207,7 @@ def _make_table(self): html.append(grid_header) - table = TABLE(**self.param.grid_class_style.get("grid-table")) + table = TABLE(_class=self.param.grid_class_style.get("grid-table")) # build the header table.append(self._make_table_header()) @@ -1485,22 +1216,30 @@ def _make_table(self): table.append(self._make_table_body()) # add the table to the html - html.append(DIV(table, **self.param.grid_class_style.get("grid-table-wrapper"))) + html.append( + DIV(table, _class=self.param.grid_class_style.get("grid-table-wrapper")) + ) # add the row counter information - footer = DIV(**self.param.grid_class_style.get("grid-footer")) - - row_count = DIV(**self.param.grid_class_style.get("grid-info")) - row_count.append( - str(self.T("Displaying rows %s thru %s of %s")) - % ( - self.page_start + 1 if self.number_of_pages > 1 else 1, - self.page_end - if self.page_end < self.total_number_of_rows - else self.total_number_of_rows, - self.total_number_of_rows, + footer = DIV(_class=self.param.grid_class_style.get("grid-footer")) + + row_count = DIV(_class=self.param.grid_class_style.get("grid-info")) + ( + row_count.append( + str(self.T("Displaying rows %s thru %s of %s")) + % ( + self.page_start + 1 if self.number_of_pages > 1 else 1, + ( + self.page_end + if self.page_end < self.total_number_of_rows + else self.total_number_of_rows + ), + self.total_number_of_rows, + ) ) - ) if self.number_of_pages > 0 else row_count.append(self.T("No rows to display")) + if self.number_of_pages > 0 + else row_count.append(self.T("No rows to display")) + ) footer.append(row_count) # build the pager @@ -1518,19 +1257,9 @@ def _make_table(self): else: override_classes = element.__dict__.get("override_classes", None) if not override_classes: - override_classes = ( - self.param.grid_class_style.classes.get( - "grid-footer-element", "" - ) - + f" {element.additional_classes}" - ) - override_styles = element.__dict__.get("override_styles", None) - if not override_styles: - override_styles = ( - self.param.grid_class_style.styles.get( - "grid-footer-element", "" - ) - + f" {element.__dict__.get('additional_styles')}" + override_classes = join_classes( + self.param.grid_class_style.get("grid-footer-element"), + element.additional_classes, ) html.append( self._make_action_button( @@ -1539,9 +1268,7 @@ def _make_table(self): icon=element.icon, icon_size="normal", additional_classes=element.additional_classes, - additional_styles=element.__dict__.get("additional_styles"), override_classes=override_classes, - override_styles=override_styles, message=element.message, name=element.__dict__.get("name"), ignore_attribute_plugin=element.ignore_attribute_plugin, @@ -1561,6 +1288,7 @@ def render(self): return self._make_table() elif self.action in ["new", "details", "edit"]: return self.form + raise HTTP(404) def xml(self): return self.render().xml() @@ -1636,37 +1364,37 @@ def get_parent(path, parent_field): :return parent_id: the id of the parent record """ parent_id = request.query.get("parent_id") + if parent_id is not None: + return int(parent_id) child_table = parent_field._table fn = parent_field.name - # find the record id of the parent from the child table record + # if not found, search the record id of the parent from the child table record if path: parts = path.split("/") record_id = parts[1] if len(parts) > 1 else None if record_id: - r = child_table(record_id) - if r: - parent_id = r[fn] - - # not passed in, check in the form - if not parent_id: - parent_id = request.forms.get(fn) - - # not found yet, check in the referer - if not parent_id: - referrer = request.query.get("_referrer") - if referrer: - kvp = base64.b16decode(referrer.encode("utf8")).decode("utf8") - if "parent_id" in kvp: - parent_id = kvp.split("parent_id=")[1] - - try: - parent_id = parent_id.split("&")[0] - except: - pass + record = child_table(record_id) + if record: + parent_id = record[fn] + if parent_id is not None: + return int(parent_id) + + # else, check in the form + parent_id = request.forms.get(fn) + if parent_id is not None: + return int(parent_id) + + # else, check in the referer + referrer = request.query.get("_referrer") + if referrer: + kvp = base64.b16decode(referrer.encode("utf8")).decode("utf8") + if "parent_id" in kvp: + parent_id = kvp.split("parent_id=")[1].split("&")[0] + return int(parent_id) - return parent_id + return None class AttributesPlugin: diff --git a/py4web/utils/jsonrpc.py b/py4web/utils/jsonrpc.py index 96ef84518..50834c4ef 100644 --- a/py4web/utils/jsonrpc.py +++ b/py4web/utils/jsonrpc.py @@ -38,10 +38,9 @@ def __call__(self, data): try: result = func(*params) if isinstance(params, list) else func(**params) return {"result": result, "id": rid, "jsonrpc": "2.0"} - except Exception as e: - error = e + except Exception as err: return { "rid": None, "jsonrpc": "2.0", - "error": {"code": -32603, "message": "Internal error", "data": e}, + "error": {"code": -32603, "message": "Internal error", "data": err}, } diff --git a/py4web/utils/mailer.py b/py4web/utils/mailer.py index 6825ff571..e53048530 100644 --- a/py4web/utils/mailer.py +++ b/py4web/utils/mailer.py @@ -8,10 +8,10 @@ """ import email.utils -import json import logging import mimetypes import os +import re import smtplib from email import message_from_string from email.encoders import encode_base64 @@ -505,7 +505,6 @@ def encoded_or_raw(text): # sign # ############################################ if sign: - import string core.check_version(None) pin = payload_in.as_string().replace("\n", "\r\n") @@ -530,7 +529,7 @@ def encoded_or_raw(text): "signed", boundary=None, _subparts=None, - **dict(micalg="pgp-sha1", protocol="application/pgp-signature") + **dict(micalg="pgp-sha1", protocol="application/pgp-signature"), ) # Insert the origin payload payload.attach(payload_in) @@ -574,7 +573,7 @@ def encoded_or_raw(text): "encrypted", boundary=None, _subparts=None, - **dict(protocol="application/pgp-encrypted") + **dict(protocol="application/pgp-encrypted"), ) p = MIMEBase("application", "pgp-encrypted") p.set_payload("Version: 1\r\n") @@ -737,7 +736,7 @@ def encoded_or_raw(text): to.extend(bcc) payload["Subject"] = encoded_or_raw(to_unicode(subject, encoding)) payload["Date"] = email.utils.formatdate() - for k, v in iteritems(headers): + for k, v in headers.items(): payload[k] = encoded_or_raw(to_unicode(v, encoding)) list_unsubscribe = list_unsubscribe or self.settings.list_unsubscribe @@ -788,7 +787,7 @@ def encoded_or_raw(text): body=to_unicode(text or "", encoding), html=html, attachments=attachments, - **xcc + **xcc, ) elif html and (not raw): result = google_mail.send_mail( @@ -797,7 +796,7 @@ def encoded_or_raw(text): subject=to_unicode(subject, encoding), body=to_unicode(text or "", encoding), html=html, - **xcc + **xcc, ) else: result = google_mail.send_mail( @@ -805,7 +804,7 @@ def encoded_or_raw(text): to=origTo, subject=to_unicode(subject, encoding), body=to_unicode(text or "", encoding), - **xcc + **xcc, ) elif self.settings.server == "aws" and boto3: client = boto3.client("ses") @@ -814,8 +813,9 @@ def encoded_or_raw(text): response = client.send_raw_email( RawMessage=raw, Source=sender, Destinations=to ) + print("Message send:", response) return True - except ClientError as e: + except ClientError: raise RuntimeError() else: smtp_args = self.settings.server.split(":") @@ -834,12 +834,12 @@ def encoded_or_raw(text): # do not want to hide errors raising some exception here try: server.quit() - except: + except Exception: pass # ensure to close any socket with SMTP server try: server.close() - except: + except Exception: pass except Exception as e: self.settings.logger.warning("Mailer.send failure:%s" % e) diff --git a/py4web/utils/misc.py b/py4web/utils/misc.py index 45af9cc76..c4802a80c 100644 --- a/py4web/utils/misc.py +++ b/py4web/utils/misc.py @@ -14,7 +14,6 @@ import base64 import hashlib import hmac -import inspect import json import logging import os @@ -28,10 +27,10 @@ import uuid import zlib -_struct_2_long_long = struct.Struct("=QQ") - from Crypto.Cipher import AES +_struct_2_long_long = struct.Struct("=QQ") + HAVE_COMPARE_DIGEST = False if hasattr(hmac, "compare_digest"): HAVE_COMPARE_DIGEST = True diff --git a/py4web/utils/populate.py b/py4web/utils/populate.py index a5039ac57..d9112c370 100644 --- a/py4web/utils/populate.py +++ b/py4web/utils/populate.py @@ -92,10 +92,10 @@ def __init__(self): def learn(self, text): replacements1 = { - "[^a-zA-Z0-9\.;:\-]": " ", - "\s+": " ", + r"[^a-zA-Z0-9\.;:\-]": " ", + r"\s+": " ", ", ": " , ", - "\. ": " . ", + r"\. ": " . ", ": ": " : ", "; ": " ; ", } @@ -124,7 +124,7 @@ def loadd(self, db): self.db = db def generate(self, length=10000, prefix=False): - replacements2 = {" ,": ",", " \.": ".\n", " :": ":", " ;": ";", "\n\s+": "\n"} + replacements2 = {" ,": ",", " \\.": ".\n", " :": ":", " ;": ";", "\n\\s+": "\n"} keys = list(self.db.keys()) key = keys[random.randint(0, len(keys) - 1)] words = key @@ -132,7 +132,7 @@ def generate(self, length=10000, prefix=False): regex = re.compile("[a-z]+") for i in range(length): okey = key - if not key in self.db: + if key not in self.db: break # should not happen db = self.db[key] s = sum(db.values()) @@ -231,7 +231,7 @@ def populate_generator(table, default=True, compute=False, contents=None, ell=No continue elif field.compute is not None: continue - elif default and not field.default in (None, ""): + elif default and field.default not in (None, ""): record[fieldname] = field.default elif compute and field.compute: continue @@ -271,7 +271,7 @@ def populate_generator(table, default=True, compute=False, contents=None, ell=No record[fieldname] = random.randint( field.requires.minimum, field.requires.maximum - 1 ) - except: + except Exception: if "day" in fieldname: record[fieldname] = random.randint(1, 28) elif "month" in fieldname: @@ -280,7 +280,7 @@ def populate_generator(table, default=True, compute=False, contents=None, ell=No record[fieldname] = random.randint(2000, 2013) else: record[fieldname] = random.randint(0, 1000) - elif field.type == "double" or str(field.type).startswith("decimal"): + elif field.type in ("float", "double") or str(field.type).startswith("decimal"): if hasattr(field.requires, "minimum"): rand = random.random() if str(field.type).startswith("decimal"): @@ -295,7 +295,7 @@ def populate_generator(table, default=True, compute=False, contents=None, ell=No elif field.type[:10] == "reference ": tablename = field.type[10:] rtable = db[tablename] - if not tablename in ids: + if tablename not in ids: if db._dbname == "gql": ids[tablename] = [x.id for x in db(rtable).select(rtable.id)] else: @@ -308,7 +308,7 @@ def populate_generator(table, default=True, compute=False, contents=None, ell=No elif field.type[:15] == "list:reference ": tablename = field.type[15:] rtable = db[tablename] - if not tablename in ids: + if tablename not in ids: if db._dbname == "gql": ids[tablename] = [x.id for x in db(rtable).select(rtable.id)] else: diff --git a/py4web/utils/publisher.py b/py4web/utils/publisher.py index 24ee3a352..6d189ee07 100644 --- a/py4web/utils/publisher.py +++ b/py4web/utils/publisher.py @@ -9,7 +9,6 @@ class Publisher: - """this is a work in progress - API subject to change""" def __init__(self, db, policy=None, auth=None, path="service/{uuid}/"): @@ -23,7 +22,6 @@ def __init__(self, db, policy=None, auth=None, path="service/{uuid}/" f = action(self.path + "/", method=["PUT", "DELETE"])(f) def api(self, tablename, id=None): - policy = self.policy data = self.restapi(request.method, tablename, id, request.query, request.json) response.status = data["code"] return data @@ -36,7 +34,6 @@ def grid(self, table): name = "vue%s" % str(uuid.uuid4())[:8] return DIV( self.mtable(table), - TAG.SCRIPT(_src=URL("static/js/axios.min.js")), TAG.SCRIPT(_src=URL("static/js/vue.min.js")), TAG.SCRIPT(_src=URL("static/js/utils.js")), TAG.SCRIPT(_src=URL("static/components/mtable.js")), diff --git a/py4web/utils/recaptcha.py b/py4web/utils/recaptcha.py index e0529739a..90dafb8a3 100644 --- a/py4web/utils/recaptcha.py +++ b/py4web/utils/recaptcha.py @@ -58,10 +58,6 @@ def fixture(self): def field(self): return Field("g_recaptcha_response", "hidden", requires=self.validator) - @property - def script(self): - return recaptcha_script(self.api_key) - def validator(self, value, _): data = {"secret": self.api_secret, "response": value} res = requests.post( @@ -71,5 +67,5 @@ def validator(self, value, _): if res.json()["success"]: return (True, None) return (False, "Invalid ReCaptcha response") - except exc: + except Exception as exc: return (False, str(exc)) diff --git a/py4web/utils/security.py b/py4web/utils/security.py index 5c0067322..8025d1f14 100644 --- a/py4web/utils/security.py +++ b/py4web/utils/security.py @@ -1,4 +1,3 @@ -import fnmatch import ipaddress from py4web.core import HTTP, Fixture, request, response @@ -19,7 +18,6 @@ def match_ip(ip, networks): class CheckHeaders(Fixture): - """ This fixture can block an action from being excuted if: - it does not match the specificated protocol (http, https) diff --git a/py4web/utils/tags.py b/py4web/utils/tags.py index 4bad91512..df3b8150a 100644 --- a/py4web/utils/tags.py +++ b/py4web/utils/tags.py @@ -1,7 +1,5 @@ import logging -from pydal.tools.tags import Tags - logging.warning( "Deprecation notice: replace py4web.utils.tags with pydal.tools.tags in your code." ) diff --git a/py4web/utils/url_signer.py b/py4web/utils/url_signer.py index d65944e3d..eb345e911 100644 --- a/py4web/utils/url_signer.py +++ b/py4web/utils/url_signer.py @@ -50,8 +50,8 @@ def on_request(self, context): if self.url_signer.lifespan is not None: if float(ts) + self.url_signer.lifespan < time.time(): raise HTTP(403) - except: - raise HTTP(403) + except Exception as err: + raise HTTP(403, body=str(err)) def _decode_ts(self, ts_string): """Decodes the timestamp, removing the salt.""" @@ -116,10 +116,6 @@ def anotherpath(): assert "_signature" not in self.variables_to_sign self.algo = algo or hashlib.sha256 - @property - def local(self): - return self._safe_local - def on_request(self, context): """Creates the signing key if necessary.""" if self.session is not None and self.session.get("_signature_key") is None: diff --git a/pyproject.toml b/pyproject.toml index d358884e3..ebed4fa8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,10 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + [project] name = "py4web" -version = "1.20230718.1" +version = "1.20241019.1" authors = [{ name="Massimo Di Pierro", email="massimo.dipierro@gmail.com" },] description = "A fast, stable, comprehensive web framework" readme = "README.rst" @@ -13,10 +17,10 @@ classifiers = [ dynamic = ["dependencies"] [tool.setuptools.dynamic] -dependencies = {file = ["requirements.txt"]} +dependencies = {file = "requirements.txt"} -[tool.setuptools] -packages = ["py4web", "py4web.utils", "py4web.utils.auth_plugins",] +[tool.setuptools] +packages = ["py4web", "py4web.server_adapters", "py4web.utils", "py4web.utils.auth_plugins"] [tool.setuptools.package-data] py4web = ["assets/*"] diff --git a/requirements.txt b/requirements.txt index ee54c3cf2..2807d563b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ wheel -ombott >= 1.0.0 +ombott==2.2 click colorama cryptography @@ -10,10 +10,10 @@ requests threadsafevariable >= 20230507.1 pyjwt >= 2.0.1 pycryptodome -pluralize >= 20230507.1 -rocket3 >= 20230507.1 +pluralize >= 20240515.1 +rocket3 >= 20241019.1 yatl >= 20230507.3 -pydal >= 20230521.1 +pydal >= 20240906.1 watchgod >= 0.6 # optional modules: diff --git a/tests/test_action.py b/tests/test_action.py index 330339fee..1e229eb2e 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -1,6 +1,8 @@ +# pylint: disable=assignment-from-none import copy import multiprocessing import os +import threading import time import unittest import uuid @@ -8,16 +10,17 @@ import mechanize import requests -from py4web import abort, action, DAL, Field, Session, Cache, HTTP, Condition -from py4web.core import bottle, request, error404, Fixture +from py4web import DAL, HTTP, Cache, Condition, Field, Session, abort, action +from py4web.core import Fixture, bottle, error404, request os.environ["PY4WEB_APPS_FOLDER"] = os.path.sep.join( os.path.normpath(__file__).split(os.path.sep)[:-2] ) +SECRET = str(uuid.uuid4()) db = DAL("sqlite://storage_%s" % uuid.uuid4(), folder="/tmp/") db.define_table("thing", Field("name")) -session = Session(secret="my secret") +session = Session(secret=SECRET) cache = Cache() action.app_name = "tests" @@ -28,23 +31,24 @@ @action.uses(db, session) @action.uses(Condition(lambda: True)) def index(): - db.thing.insert(name="test") + new_id = db.thing.insert(name="test") session["number"] = session.get("number", 0) + 1 # test copying Field ThreadSafe attr db.thing.name.default = "test_clone" - field_clone = copy.copy(db.thing.name) - clone_ok = 1 if field_clone.default == db.thing.name.default == "test_clone" else 0 - return "ok %s %s %s" % (session["number"], db(db.thing).count(), clone_ok) + return "ok %s %s %s" % (session["number"], db(db.thing).count(), new_id) + def fail(): raise HTTP(404) + @action("conditional") @action.uses(Condition(lambda: False, on_false=fail)) def conditional(): return "OK" + @action("raise300") def raise300(): raise HTTP(300) @@ -72,7 +76,7 @@ def on_error(self, context): @action("abort_caught") @action.uses(corrector) -def abort_response(): +def abort_response_corrected(): abort(400) @@ -80,6 +84,20 @@ def run_server(): bottle.run(host="localhost", port=8001) +class FieldTest(unittest.TestCase): + """Check that we chat we can safely clone Field(s)""" + + def test_fiel_clone(self): + def test(): + db.thing.name.default = "test" + field_clone = copy.copy(db.thing.name) + assert field_clone.default == db.thing.name.default == "test" + + thread = threading.Thread(target=test) + thread.start() + thread.join() + + class CacheAction(unittest.TestCase): def setUp(self): self.server = multiprocessing.Process(target=run_server) @@ -100,30 +118,29 @@ def test_action(self): time.sleep(2) res = self.browser.open("http://127.0.0.1:8001/tests/index") - self.assertEqual(res.read(), b"ok 2 2 1") + self.assertEqual(res.read(), b"ok 2 2 2") def test_error(self): - res = requests.get("http://127.0.0.1:8001/tests/conditional") + res = requests.get("http://127.0.0.1:8001/tests/conditional", timeout=5) self.assertEqual(res.status_code, 404) - res = requests.get("http://127.0.0.1:8001/tests/raise300") + res = requests.get("http://127.0.0.1:8001/tests/raise300", timeout=5) self.assertEqual(res.status_code, 300) - res = requests.get("http://127.0.0.1:8001/tests/abort") + res = requests.get("http://127.0.0.1:8001/tests/abort", timeout=5) self.assertEqual(res.status_code, 400) - res = requests.get("http://127.0.0.1:8001/tests/abort_caught") + res = requests.get("http://127.0.0.1:8001/tests/abort_caught", timeout=5) self.assertEqual(res.status_code, 200) self.assertEqual(res.content, b"caught") - res = requests.get("http://127.0.0.1:8001/tests/bottle_httpresponse") + res = requests.get("http://127.0.0.1:8001/tests/bottle_httpresponse", timeout=5) self.assertEqual(res.status_code, 200) self.assertEqual(res.content, b"ok") def test_local(self): # for test coverage request.app_name = "example" - Session.__init_request_ctx__() # mimic before_request-hook index() def test_error_page(self): diff --git a/tests/test_auth.py b/tests/test_auth.py index 9e2f06243..5327e93dc 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,16 +1,19 @@ +import io import os import unittest -from py4web.core import Session, DAL, request, HTTP, Field, bottle, _before_request +import uuid + +from py4web.core import DAL, HTTP, Field, Fixture, Session, bottle, request, safely from py4web.utils.auth import Auth, AuthAPI +SECRET = str(uuid.uuid4()) + class TestAuth(unittest.TestCase): def setUp(self): os.environ["PY4WEB_APPS_FOLDER"] = "apps" - _before_request() # mimic before_request bottle-hook self.db = DAL("sqlite:memory") - self.session = Session(secret="a", expiration=10) - self.session.initialize() + self.session = Session(secret=SECRET, expiration=10) self.auth = Auth( self.session, self.db, define_tables=True, password_complexity=None ) @@ -19,30 +22,36 @@ def setUp(self): request.app_name = "_scaffold" def tearDown(self): + # this is normally done by @action + safely(lambda: Fixture.local_delete(self.session)) bottle.app.router.remove("/*") def action(self, name, method, query, data): request.environ["REQUEST_METHOD"] = method request.environ["ombott.request.query"] = query request.environ["ombott.request.json"] = data + request.environ["wsgi.input"] = io.BytesIO() # we break a symmetry below. should fix in auth.py if name.startswith("api/"): return getattr(AuthAPI, name[4:])(self.auth) - else: - return getattr(self.auth.form_source, name)() - - def on_request(self, context={}, keep_session=False): - storage = self.session._safe_local + return getattr(self.auth.form_source, name)() - # mimic before_request bottle-hook - _before_request() - - # mimic action.uses() - self.session.initialize() + def on_request(self, context=None, keep_session=False): + # store the current session + context = context or {} + try: + storage = self.session.local.__dict__ + except RuntimeError: + storage = None + # reinitialize everything + safely(lambda: Fixture.local_delete(self.session)) + safely(lambda: Fixture.local_delete(self.auth.flash)) + self.session.on_request(context) self.auth.flash.on_request(context) self.auth.on_request(context) - if keep_session: - self.session._safe_local = storage + # restore the previous session + if keep_session and storage: + self.session.local.__dict__.update(storage) def test_extra_fields(self): db = DAL("sqlite:memory") @@ -55,15 +64,16 @@ def test_extra_fields(self): def test_register_invalid(self): self.on_request() body = {"email": "pinco.pallino@example.com"} + res = self.auth.action("api/register", "POST", {}, body) self.assertEqual( - self.auth.action("api/register", "POST", {}, body), + res, { "id": None, "errors": { - "username": "Enter a value", - "password": "Too short", - "first_name": "Enter a value", - "last_name": "Enter a value", + "username": "required", + "password": "required", + "first_name": "required", + "last_name": "required", }, "status": "error", "message": "validation errors", @@ -95,7 +105,6 @@ def test_register(self): {"status": "error", "message": "Invalid Credentials", "code": 400}, ) - self.on_request() self.on_request() body = {"email": "pinco.pallino@example.com", "password": "123456789"} self.assertEqual( @@ -224,18 +233,6 @@ def test_register(self): {"updated": 1, "status": "success", "code": 200}, ) - self.on_request(keep_session=True) - body = {"first_name": "Max", "last_name": "Powers", "password": "xyz"} - self.assertEqual( - self.auth.action("api/profile", "POST", {}, body), - { - "errors": {"password": "invalid"}, - "status": "error", - "message": "validation errors", - "code": 401, - }, - ) - self.on_request(keep_session=True) body = {"first_name": "Max", "last_name": "Powers"} self.assertEqual( diff --git a/tests/test_cache.py b/tests/test_cache.py index f9fd065a8..e5d910836 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -18,7 +18,7 @@ def test_logic(self): def test_different_keys(self): cache = py4web.Cache() results = set() - for k in range(100): + for _ in range(100): results.add(cache.get("a", random.random)) results.add(cache.get("b", random.random)) results.add(cache.get("c", random.random)) @@ -29,7 +29,9 @@ def test_change_detection(self): results = set() for k in range(30): results.add( - cache.get("a", random.random, expiration=0, monitor=lambda: int(k / 10)) + cache.get( + "a", random.random, expiration=0, monitor=lambda k=k: int(k / 10) + ) ) self.assertEqual(len(results), 3) time.sleep(0.02) @@ -42,7 +44,7 @@ def test_timing(self): for k in range(M): cache.get(k, random.random) t0 = time.time() - for k in range(N): + for _ in range(N): cache.get("new", random.random) self.assertTrue((time.time() - t0) / N, 1 - 5) self.assertTrue(cache.free == 0) @@ -55,11 +57,11 @@ def f(x): return x + random.random() results = set() - for k in range(10): + for _ in range(10): results.add(f(1)) results.add(f(2)) time.sleep(0.2) - for k in range(10): + for _ in range(10): results.add(f(1)) results.add(f(2)) self.assertEqual(len(results), 4) diff --git a/tests/test_fixture.py b/tests/test_fixture.py index b1bbe7d5e..e1b4cbc26 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -1,9 +1,7 @@ -from types import SimpleNamespace -import pytest import threading -from py4web.core import Fixture +import pytest -result = {"seq": []} +from py4web.core import Fixture def run_thread(func, *a): @@ -12,41 +10,37 @@ def run_thread(func, *a): class Foo(Fixture): - def on_request(self): - self._safe_local = SimpleNamespace() + def on_request(self, context): + Fixture.local_initialize(self) @property def bar(self): - return self._safe_local.a + return self.local.a @bar.setter def bar(self, a): - self._safe_local.a = a + self.local.a = a +results = {} foo = Foo() -def before_request(): - Fixture.__init_request_ctx__() - - @pytest.fixture def init_foo(): def init(key, a, evnt_done=None, evnt_play=None): - result["seq"].append(key) - before_request() - foo.on_request() + Fixture.local_initialize(foo) foo.bar = a evnt_done and evnt_done.set() evnt_play and evnt_play.wait() - result[key] = foo.bar + results[key] = foo.bar + Fixture.local_delete(foo) return foo return init -def test_fixtute_local_storage(init_foo): +def test_fixture_local_storage(init_foo): assert init_foo("t1", "a1") is foo evnt_done = threading.Event() evnt_play = threading.Event() @@ -58,16 +52,14 @@ def test_fixtute_local_storage(init_foo): t3.join() evnt_play.set() t2.join() - assert foo.bar == "a1" - assert result["t2"] == "a2" - assert result["t3"] == "a3" - assert ",".join(result["seq"]) == "t1,t2,t3" + assert results["t1"] == "a1" + assert results["t2"] == "a2" + assert results["t3"] == "a3" -def test_fixtute_error(): - before_request() +def test_fixture_error(): # attempt to access _safe_local prop without on_request-call with pytest.raises(RuntimeError) as err: foo.bar - assert "py4web hint" in err.value.args[0] + assert "not initialized" in err.value.args[0] assert "Foo object" in err.value.args[0] diff --git a/tests/test_form.py b/tests/test_form.py index 7762e59f8..56876c301 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -1,9 +1,12 @@ import io -import os import unittest -from py4web import request, response, Field, Session +import uuid + +from py4web import Field, Session, request, response from py4web.utils.form import Form +SECRET = str(uuid.uuid4()) + class FormTest(unittest.TestCase): def setUp(self): @@ -12,11 +15,12 @@ def setUp(self): response._cookies = "" def test_form(self): - session = Session() - session.initialize() + session = Session(secret=SECRET) + session.on_request({}) table = [Field("name")] form_name = "testing_form" f = Form(table, form_name=form_name, csrf_session=session) value = f.formkey post_vars = dict(_formname=form_name, _formkey=value) self.assertTrue(f._verify_form(post_vars)) + session.on_success({}) diff --git a/tests/test_get_error_snapshot.py b/tests/test_get_error_snapshot.py index 41a42a2ef..9c6b9afcc 100644 --- a/tests/test_get_error_snapshot.py +++ b/tests/test_get_error_snapshot.py @@ -1,5 +1,5 @@ -import os import unittest + from py4web.core import get_error_snapshot @@ -10,16 +10,16 @@ def test_get_error_snapshot(self): except Exception: snapshot = get_error_snapshot() keys = list(sorted(snapshot.keys())) - self.assertEqual( - keys, - [ - "exception_type", - "exception_value", - "os_environ", - "platform_info", - "python_version", - "stackframes", - "timestamp", - "traceback", - ], - ) + self.assertEqual( + keys, + [ + "exception_type", + "exception_value", + "os_environ", + "platform_info", + "python_version", + "stackframes", + "timestamp", + "traceback", + ], + ) diff --git a/tests/test_json.py b/tests/test_json.py index 10a1a4bcd..ddc5b45bb 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -1,7 +1,7 @@ import datetime +import fractions import json import unittest -import fractions from py4web.core import dumps, objectify @@ -10,7 +10,7 @@ class TestJson(unittest.TestCase): def test_objectify(self): """Check if we can serialize objects, generators, and dates""" - class A(object): + class A: def __init__(self, x): self.x = x diff --git a/tests/test_main.py b/tests/test_main.py index 61fd3961d..61a613576 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,10 +1,12 @@ import os -import unittest -import tempfile import signal -from py4web.core import cli +import tempfile +import unittest + from click.testing import CliRunner +from py4web.core import cli + def run_cli(): dirpath = tempfile.mkdtemp() @@ -24,9 +26,6 @@ def run_cli(): class MainTest(unittest.TestCase): def test_main(self): - class MyException(Exception): - pass - def handler(signum, frame): raise KeyboardInterrupt diff --git a/tests/test_session.py b/tests/test_session.py index ccd493361..9c16a21c6 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,85 +1,117 @@ +import contextlib import io -import unittest +import subprocess import time +import unittest +import uuid + import memcache -import subprocess import pytest -from py4web import request, response, Session, DAL -from py4web.core import _before_request +from py4web import DAL, Session, request, response +from py4web.core import Fixture from py4web.utils.dbstore import DBStore + def unquote(text): print(repr(text)) if text[:1] + text[-1:] == '""': text = text[1:-1] return text -class TestSession(unittest.TestCase): - def setUp(self): + +secret1 = str(uuid.uuid4()) +secret2 = str(uuid.uuid4()) + + +@contextlib.contextmanager +def request_context(session, context={}): + try: request.environ["wsgi.input"] = io.StringIO() - _before_request() # mimic before_request bottle-hook - request.cookies.clear() response._cookies = "" + yield session.on_request(context) + session.on_success(context) + finally: + session.on_error(context) + Fixture.local_delete(session) + +class TestSession(unittest.TestCase): def test_session(self): request.app_name = "myapp" - session = Session(secret="a", expiration=10) - context = dict(status=200) - session.on_request(context) - session["key"] = "value" - cookie_name = session.local.session_cookie_name - session.on_success(context) + session = Session(secret=secret1, expiration=10) + + with request_context(session): + session["key"] = "value" + assert "key" in session.local.data + a, b = str(response._cookies)[len("Set-Cookie: ") :].split(";")[0].split("=", 1) b = unquote(b) request.cookies[a] = b - _before_request() - with pytest.raises(RuntimeError) as err: - session.local - self.assertTrue("py4web hint" in str(err.value)) + session = Session(secret=secret2, expiration=10) + request.cookies[a] = b + with request_context(session): + self.assertEqual(session.get("key"), None) + + session = Session(secret=secret1, expiration=10) + request.cookies[a] = b + with request_context(session): + self.assertEqual(session.get("key"), "value") + + def test_session_as_attributes(self): + request.app_name = "myapp" + session = Session(secret=secret1, expiration=10) + + with request_context(session): + session.key = "value" + assert "key" in session.local.data - session = Session(secret="b", expiration=10) + a, b = str(response._cookies)[len("Set-Cookie: ") :].split(";")[0].split("=", 1) + b = unquote(b) + request.cookies[a] = b + + session = Session(secret=secret2, expiration=10) request.cookies[a] = b - context = dict(status=200) - session.on_request(context) - self.assertEqual(session.get("key"), None) + with request_context(session): + self.assertEqual(session.key, None) - session = Session(secret="a", expiration=10) + session = Session(secret=secret1, expiration=10) request.cookies[a] = b - session.on_request(context) - self.assertEqual(session.get("key"), "value") + with request_context(session): + self.assertEqual(session.key, "value") + + def test_session_not_initialized(self): + request.app_name = "myapp" + session = Session(secret=secret1, expiration=10) + + with pytest.raises(RuntimeError) as err: + session.local + self.assertTrue("not initialized" in str(err.value)) def test_session_in_db(self): request.app_name = "myapp" db = DAL("sqlite:memory") - session = Session(secret="a", expiration=10, storage=DBStore(db)) - request.cookies.clear() - context = dict(status=200) - session.on_request(context) - session["key"] = "value" - cookie_name = session.local.session_cookie_name - session.on_success(context) + session = Session(secret=secret1, expiration=10, storage=DBStore(db)) + with request_context(session): + session["key"] = "value" a, b = str(response._cookies)[len("Set-Cookie: ") :].split(";")[0].split("=", 1) b = unquote(b) request.cookies[a] = b - _before_request() with pytest.raises(RuntimeError) as err: session.local - self.assertTrue("py4web hint" in str(err.value)) + self.assertTrue("not initialized" in str(err.value)) session = Session(expiration=10, storage=DBStore(db)) request.cookies[a] = b - context = dict(status=200) - session.on_request(context) - self.assertEqual(session.get("key"), "value") + with request_context(session): + self.assertEqual(session.get("key"), "value") session = Session(expiration=10, storage=DBStore(db)) request.cookies[a] = "wrong_cookie" - context = dict(status=200) - session.on_request(context) - self.assertEqual(session.get("key"), None) + with request_context(session): + self.assertEqual(session.get("key"), None) def test_session_in_memcache(self): memcache_process = None @@ -88,13 +120,10 @@ def test_session_in_memcache(self): time.sleep(1) request.app_name = "myapp" conn = memcache.Client(["127.0.0.1:11211"], debug=0) - session = Session(secret="a", expiration=10, storage=conn) + session = Session(secret=secret1, expiration=10, storage=conn) request.cookies.clear() - context = dict(status=200) - session.on_request(context) - session["key"] = "value" - cookie_name = session.local.session_cookie_name - session.on_success(context) + with request_context(session): + session["key"] = "value" a, b = ( str(response._cookies)[len("Set-Cookie: ") :] @@ -104,24 +133,22 @@ def test_session_in_memcache(self): b = unquote(b) request.cookies[a] = b - _before_request() with pytest.raises(RuntimeError) as err: session.local - self.assertTrue("py4web hint" in str(err.value)) + self.assertTrue("not initialized" in str(err.value)) conn = memcache.Client(["127.0.0.1:11211"], debug=0) session = Session(expiration=10, storage=conn) request.cookies[a] = b - context = dict(status=200) - session.on_request(context) - self.assertEqual(session.get("key"), "value") + with request_context(session): + self.assertEqual(session.get("key"), "value") conn = memcache.Client(["127.0.0.1:11211"], debug=0) session = Session(expiration=10, storage=conn) request.cookies[a] = "wrong_cookie" - context = dict(status=200) - session.on_request(context) - self.assertEqual(session.get("key"), None) + + with request_context(session): + self.assertEqual(session.get("key"), None) finally: if memcache_process is None: print("memcached not availabl, test skipped") diff --git a/tests/test_tags.py b/tests/test_tags.py index be685cbb6..9f17d0c81 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -1,4 +1,5 @@ import unittest + from pydal import DAL, Field from pydal.tools.tags import Tags diff --git a/tests/test_template.py b/tests/test_template.py index 041e43718..ff996493a 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -1,6 +1,7 @@ import os import unittest -from py4web.core import Template, request + +from py4web.core import Template PATH = os.path.join(os.path.dirname(__file__), "templates") diff --git a/tests/test_url.py b/tests/test_url.py index 68d40a2cf..993bf848c 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -1,8 +1,6 @@ -import random -import time import unittest -from py4web import request, URL +from py4web import URL, request class TestURL(unittest.TestCase): @@ -12,3 +10,7 @@ def test_url(self): request.app_name = "app" self.assertEqual(URL("index"), "/app/index") self.assertEqual(URL("a", "b", vars=dict(x=1), hash="y"), "/app/a/b?x=1#y") + + +if __name__ == "__main__": + unittest.main()