Compare commits

...
56 Commits
Author SHA1 Message Date
mdipierro 4e704ca6f7 R-2.15.1 2017-07-10 16:18:35 -05:00
mdipierro 047ed786ac fixed some error in hashlib_md5 2017-07-10 16:02:15 -05:00
mdipierro ca1e5156ba fixed hashlib_md5 again, thanks Paolo 2017-07-10 15:10:16 -05:00
mdipierro 4854b84ff9 fixed a python 3 problem, thanks kato 2017-07-10 14:54:08 -05:00
mdipierro aa252cdbd8 Merge branch 'BuhtigithuB-improve/pep8-globals-py' 2017-07-10 14:20:45 -05:00
mdipierro e3ec4d4075 merged hashlib conflict 2017-07-10 14:20:39 -05:00
mdipierro 81b000d47a Merge branch 'master' of github.com:web2py/web2py 2017-07-10 14:19:08 -05:00
mdipierro 305dac4976 fixed changelog 2017-07-10 14:19:01 -05:00
mdipierro 876cc634a8 Merge pull request #1679 from leonelcamara/fix1675
Fixes #1675 dropdown not working in the "manage" button
2017-07-10 14:18:17 -05:00
mdipierro c004d2c16e Merge pull request #1678 from willimoa/issue/1674
Issue/1674
2017-07-10 14:17:56 -05:00
mdipierro e3cce4d752 Merge pull request #1669 from ilvalle/testing_pypy3_trusty
Testing pypy3.5 in travis, switching to ubuntu trusty
2017-07-10 14:17:20 -05:00
mdipierro 2c84b88466 Merge pull request #1668 from ilvalle/fix_compile_views
fix performance regression with compiled views
2017-07-10 14:17:04 -05:00
mdipierro fe652c851b Merge pull request #1667 from BuhtigithuB/improve/pep8-main-py
Enhance main.py PEP8
2017-07-10 14:16:09 -05:00
mdipierro c183c7b18b Merge pull request #1666 from abastardi/ajax-uploads
Enable Ajax file uploads
2017-07-10 14:15:01 -05:00
mdipierro e2c9875cd5 Merge pull request #1665 from BuhtigithuB/clean/python-2-6-remaining-code-in-import-all-py
Close #1664 remaining py2.6 specific code
2017-07-10 14:14:44 -05:00
mdipierro 579c54f926 Merge pull request #1663 from BuhtigithuB/improve/pep8-http-py
Enhance http.py PEP8
2017-07-10 14:14:09 -05:00
mdipierro b2cb0bc189 Merge pull request #1662 from BuhtigithuB/improve/pep8-html-py
Enhance html.py PEP8
2017-07-10 14:13:53 -05:00
mdipierro 3f6e8c755a Merge pull request #1660 from BuhtigithuB/improve/pep8-highlight-py
Enhance highlight.py PEP8
2017-07-10 14:13:19 -05:00
mdipierro 023d7f3e6c Merge pull request #1659 from BuhtigithuB/improve/pep8-decoder-py
Enhance decoder.py PEP8
2017-07-10 14:13:04 -05:00
mdipierro 01285ad4cd Merge pull request #1657 from BuhtigithuB/improve/pep8-dal-py
Enhance dal.py PEP8
2017-07-10 14:12:55 -05:00
mdipierro 0d855c1e9c Merge pull request #1656 from BuhtigithuB/improve/pep8-contenttype-py
Enhance contenttype.py PEP8
2017-07-10 14:12:45 -05:00
mdipierro 2d21c00e8d Merge pull request #1655 from BuhtigithuB/improve/pep8-compileapp-py
Enhance compileapp.py PEP8
2017-07-10 14:12:30 -05:00
mdipierro de9d0eb895 Merge pull request #1654 from BuhtigithuB/improve/pep8-admin-py
Enhance admin.py PEP8
2017-07-10 14:12:16 -05:00
mdipierro 9998916ef6 Merge pull request #1653 from BuhtigithuB/improve/pep8-validators-py
Enhance validators.py PEP8 and fix docstring py3 compatibility
2017-07-10 14:11:58 -05:00
mdipierro 453123a8ed Merge pull request #1652 from BuhtigithuB/improve/pep8-tools-py
Enhance tools.py PEP8 compliancy
2017-07-10 14:11:11 -05:00
mdipierro 2396cad2d1 Merge pull request #1658 from ilvalle/fix_minify
fix minify, added tests
2017-07-10 14:09:53 -05:00
mdipierro 540eecc207 R-2.15.0b2 2017-07-10 03:23:13 -05:00
mdipierro 83681f3f5d fixed markmin test, change beahvior 2017-07-10 03:16:42 -05:00
mdipierro b0a01ef720 fixed some relative imports, thanks steve.van.christen 2017-07-10 03:13:12 -05:00
mdipierro da5543f62e tracking newest pydal 2017-07-10 03:06:08 -05:00
mdipierro dc4ff7c3cc made markmin2html independens on gluon again and fixed reported problem, thanks mweissen 2017-07-10 03:01:35 -05:00
mdipierro 0223b0871e jquery 2.2.4 in admin 2017-07-10 02:43:56 -05:00
Leonel Câmara c6c027dbec Fixes #1675 dropdown not working in the "manage" button 2017-07-07 18:10:31 +01:00
Andrew Willimott 60edb11420 Explicit path added for graph css file. 2017-07-07 21:03:55 +12:00
Andrew Willimott 51ce3ffd36 Path to css file changed to explicitly point to admin app 2017-07-07 20:39:42 +12:00
mdipierro 89832479fc R-2.16.0b1 2017-07-05 01:48:28 -05:00
mdipierro 15ffdd9a20 R-2.16.0b1 2017-07-05 01:46:01 -05:00
mdipierro 8505c7b282 R-2.16.0b1 2017-07-05 01:42:59 -05:00
ilvalle af7bfac1e2 Testing pypy3.5 in travis, switching to ubuntu trusty 2017-06-24 08:07:50 +02:00
ilvalle 054320820c fix performance regression with compiled views 2017-06-24 07:21:23 +02:00
Richard Vézina 1a52b0ee3b Enhance main.py PEP8 2017-06-22 09:46:55 -04:00
abastardi 78f3af6fc1 Use FormData when supported to allow Ajax file uploads 2017-06-21 16:29:19 -04:00
Richard Vézina b5b98d6e19 Close #1664 remaining py2.6 specific code 2017-06-21 15:32:28 -04:00
Richard Vézina aa1b71e431 Enhance http.py PEP8 2017-06-21 15:14:41 -04:00
Richard Vézina 472c0ff2fb Enhance html.py PEP8 2017-06-21 15:08:52 -04:00
Richard Vézina 58bedd4c1a Enhance highlight.py PEP8 2017-06-21 14:54:59 -04:00
Richard Vézina 88790dcaee Enhance globals.py PEP8 2017-06-21 14:49:10 -04:00
Richard Vézina a8fb41333b Enhance decoder.py PEP8 2017-06-21 14:38:52 -04:00
Richard Vézina 8d5464692f Enhance dal.py PEP8 2017-06-21 13:41:10 -04:00
Richard Vézina 2080e0460f Enhance contenttype.py PEP8 2017-06-21 13:32:42 -04:00
Richard Vézina 7f5fc798c5 Enhance compileapp.py PEP8 2017-06-21 13:30:02 -04:00
Richard Vézina 833cb03ee1 Enhance admin.py PEP8 2017-06-21 13:22:30 -04:00
Richard Vézina 590de9c890 Enhance validators.py PEP8 and fix docstring py3 compatibility 2017-06-21 11:57:31 -04:00
Richard Vézina 583d106104 Fix docstring py3 compatibility issues print -> print() 2017-06-21 11:33:00 -04:00
Richard Vézina 7ada2cf89a Enhance tools.py PEP8 compliancy 2017-06-21 11:27:54 -04:00
ilvalle 9f79dccb05 fix minify, added tests 2017-06-20 22:12:43 +02:00
31 changed files with 394 additions and 280 deletions

No files matched your search

+11 -1
View File
@@ -4,11 +4,18 @@ sudo: required
cache: pip
dist: "trusty"
python:
- '2.7'
- 'pypy'
- '3.5'
- '3.6'
- 'pypy-5.3.1'
- 'pypy3.5-5.7.1-beta'
matrix:
allow_failures:
- python: 'pypy3.5-5.7.1-beta'
install:
- pip install -e .
@@ -33,3 +40,6 @@ notifications:
addons:
postgresql: "9.4"
apt:
packages:
- postgresql-9.4-postgis-2.3
+14 -16
View File
@@ -1,7 +1,11 @@
## 2.16.0b1
## 2.15.0b1
- dropped support for python 2.6
- dropped web shell
- experimental python 3 support
- experimental authapi for service login
- allow ajax file uploads
- more tests
- more pep8 compliance
- d3.js model visulization
- improved scheduler
- is_email support for internationalized Domain Names
@@ -20,6 +24,13 @@
- Updated fpdf to latest version
- JWT support
- import fabfile for remote deployment
- scheduler new feature: you can now specify intervals with cron
- gluon/* removed from sys.path. Applications relying on statements like e.g.
"from storage import Storage"
will need to be rewritten with
"from gluon.storage import Storage"
- tests can only be run with the usual web2py.py --run_system_tests OR with
python -m unittest -v gluon.tests on the root dir
- jQuery 3.2.1
- PyDAL 17.07 including:
allow jsonb support for postgres
@@ -30,21 +41,8 @@
improved Teradata support
improved mongodb support
overall refactoring
experimental support for Google Cloud SQL v2 (TODO)
## 2.15.x
- web2py does not support python 2.6 anymore
- py3.5 syntax compatible (see #1353 for details)
- dropped web shell from admin
- scheduler new feature: you can now specify intervals with cron
- gluon/* removed from sys.path. Applications relying on statements like e.g.
"from storage import Storage"
will need to be rewritten with
"from gluon.storage import Storage"
- tests can only be run with the usual web2py.py --run_system_tests OR with
python -m unittest -v gluon.tests on the root dir
- updated pymysql driver
experimental support for Google Cloud SQL v2
new pymysql driver
## 2.14.6
+2 -2
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.15.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
@@ -54,7 +54,7 @@ src:
### build web2py_src.zip
echo '' > NEWINSTALL
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
mdp:
make src
+1 -1
View File
@@ -1 +1 @@
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48
Version 2.15.1-stable+timestamp.2017.07.10.16.17.24
+2 -2
View File
@@ -690,6 +690,6 @@ def d3_graph_model():
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('static','js/d3.min.js'))
response.files.append(URL('static','js/d3_graph.js'))
response.files.append(URL('admin','static','js/d3.min.js'))
response.files.append(URL('admin','static','js/d3_graph.js'))
return dict(databases=databases, nodes=nodes, links=links)
+4 -4
View File
@@ -1,4 +1,4 @@
/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */
!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={IDLine truncated
a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e=Line truncated
null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?bLine truncated
/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new ReLine truncated
}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);forLine truncated
void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g,hb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(hb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var ib=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!ib.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),l=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},f||!o.trigger||o.trigger.apply(e,c)!==!1)){if(!f&&!o.noBubble&&!n.isWindow(e)){for(j=o.delegateType||q,ib.test(j+q)||(h=h.parentNode);h;h=h.parentNode)p.push(h),i=h;i===(e.ownerDocument||d)&&p.push(i.defaultView||i.parentWindow||a)}g=0;while((h=p[g++])&&!b.isPropagationStopped())b.type=g>1?j:o.bindType||q,m=(N.get(h,"events")||{})[b.type]&&N.get(h,"handle"),m&&m.apply(h,c),m=l&&h[l],m&&m.apply&&L(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=q,f||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==Line truncated
+1 -1
View File
@@ -239,7 +239,7 @@
{{=T("No databases in this application")}}
{{else:}}
<div id="vis"></div>
<link rel="stylesheet" href="{{=URL('static','css/d3_graph.css')}}"/>
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
<script>
// Define the d3 input data
{{from gluon.serializers import json }}
+1 -1
View File
@@ -46,7 +46,7 @@
</td>
<td>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<a class="btn dropdown-toggle" data-toggle="dropdown">
{{=T('Manage')}}
<span class="caret"></span>
</a>
+1 -1
View File
@@ -239,7 +239,7 @@
{{=T("No databases in this application")}}
{{else:}}
<div id="vis"></div>
<link rel="stylesheet" href="{{=URL('static','css/d3_graph.css')}}"/>
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
<script>
// Define the d3 input data
{{from gluon.serializers import json }}
+2 -2
View File
@@ -20,8 +20,8 @@
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath
from languages import read_possible_languages
from gluon.fileutils import abspath
from gluon.languages import read_possible_languages
possible_languages = read_possible_languages(abspath('applications', app))
# ----------------------------------------------------------------------------------------------------------------------
+28 -8
View File
@@ -12,6 +12,8 @@
$.error('web2py.js has already been loaded!');
}
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -320,7 +322,15 @@
form.submit(function (e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash();
web2py.ajax_page('post', url, form.serialize(), target, form);
var formData;
if (FORMDATA_IS_SUPPORTED) {
formData = new FormData(form[0]); // Allows file uploads.
} else {
formData = form.serialize(); // Fallback for older browsers.
}
web2py.ajax_page('post', url, formData, target, form);
e.preventDefault();
});
form.on('click', web2py.formInputClickSelector, function (e) {
@@ -339,11 +349,18 @@
if (web2py.isUndefined(element)) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
/* processData and contentType must be set to false when passing a FormData
object to jQuery.ajax. */
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
$.ajax({
'type': method,
'url': action,
'data': data,
'processData': !isFormData,
'contentType': contentType,
'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target);
@@ -699,8 +716,9 @@
});
},
/* Disables form elements:
- Does not disable elements with 'data-w2p_disable' attribute
- Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Replaces element text with value of 'data-w2p_disable_with' attribute
- Sets disabled property to true
*/
disableFormElements: function (form) {
@@ -712,13 +730,15 @@
if (!web2py.isUndefined(disable)) {
return false;
}
if (web2py.isUndefined(disable_with)) {
element.data('w2p_disable_with', element[method]());
if (!element.is(':file')) { // Altering file input values is not allowed.
if (web2py.isUndefined(disable_with)) {
element.data('w2p_disable_with', element[method]());
}
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
}
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true);
});
},
+1 -1
View File
@@ -239,7 +239,7 @@
{{=T("No databases in this application")}}
{{else:}}
<div id="vis"></div>
<link rel="stylesheet" href="{{=URL('static','css/d3_graph.css')}}"/>
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
<script>
// Define the d3 input data
{{from gluon.serializers import json }}
+15 -10
View File
@@ -54,7 +54,8 @@ def app_pack(app, request, raise_ex=False, filenames=None):
"""
try:
if filenames is None: app_cleanup(app, request)
if filenames is None:
app_cleanup(app, request)
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
w2p_pack(filename, apath(app, request), filenames=filenames)
return filename
@@ -104,7 +105,8 @@ def app_cleanup(app, request):
if os.path.exists(path):
for f in os.listdir(path):
try:
if f[:1] != '.': os.unlink(os.path.join(path, f))
if f[:1] != '.':
os.unlink(os.path.join(path, f))
except IOError:
r = False
@@ -113,7 +115,8 @@ def app_cleanup(app, request):
if os.path.exists(path):
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
if f[:1] != '.':
recursive_unlink(os.path.join(path, f))
except (OSError, IOError):
r = False
@@ -123,7 +126,8 @@ def app_cleanup(app, request):
CacheOnDisk(folder=path).clear()
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
if f[:1] != '.':
recursive_unlink(os.path.join(path, f))
except (OSError, IOError):
r = False
return r
@@ -175,10 +179,9 @@ def app_create(app, request, force=False, key=None, info=False):
return False
try:
w2p_unpack('welcome.w2p', path)
for subfolder in [
'models', 'views', 'controllers', 'databases',
'modules', 'cron', 'errors', 'sessions', 'cache',
'languages', 'static', 'private', 'uploads']:
for subfolder in ['models', 'views', 'controllers', 'databases',
'modules', 'cron', 'errors', 'sessions', 'cache',
'languages', 'static', 'private', 'uploads']:
subpath = os.path.join(path, subfolder)
if not os.path.exists(subpath):
os.mkdir(subpath)
@@ -368,7 +371,7 @@ def unzip(filename, dir, subfolder=''):
for name in sorted(zf.namelist()):
if not name.startswith(subfolder):
continue
#print name[n:]
# print name[n:]
if name.endswith('/'):
folder = os.path.join(dir, name[n:])
if not os.path.exists(folder):
@@ -435,6 +438,7 @@ def add_path_first(path):
if not global_settings.web2py_runtime_gae:
site.addsitedir(path)
def try_mkdir(path):
if not os.path.exists(path):
try:
@@ -444,11 +448,12 @@ def try_mkdir(path):
else:
os.mkdir(path)
except OSError as e:
if e.strerror == 'File exists': # In case of race condition.
if e.strerror == 'File exists': # In case of race condition.
pass
else:
raise e
def create_missing_folders():
if not global_settings.web2py_runtime_gae:
for path in ('applications', 'deposit', 'site-packages', 'logs'):
+42 -29
View File
@@ -41,11 +41,12 @@ import imp
import logging
import types
from functools import reduce
logger = logging.getLogger("web2py")
from gluon import rewrite
from gluon.custom_import import custom_import_install
import py_compile
logger = logging.getLogger("web2py")
is_pypy = settings.global_settings.is_pypy
is_gae = settings.global_settings.web2py_runtime_gae
is_jython = settings.global_settings.is_jython
@@ -111,7 +112,7 @@ class mybuiltin(object):
NOTE could simple use a dict and populate it,
NOTE not sure if this changes things though if monkey patching import.....
"""
#__builtins__
# __builtins__
def __getitem__(self, key):
try:
return getattr(builtin, key)
@@ -185,7 +186,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
else:
statement = "$.web2py.component('%s','%s');" % (url, target)
attr['_data-w2p_remote'] = url
if not target is None:
if target is not None:
return DIV(content, **attr)
else:
@@ -211,7 +212,8 @@ def LOAD(c=None, f='index', args=None, vars=None,
request.env.path_info
other_request.cid = target
other_request.env.http_web2py_component_element = target
other_request.restful = types.MethodType(request.restful.__func__, other_request) # A bit nasty but needed to use LOAD on action decorates with @request.restful()
other_request.restful = types.MethodType(request.restful.__func__, other_request)
# A bit nasty but needed to use LOAD on action decorates with @request.restful()
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
other_environment = copy.copy(current.globalenv) # NASTY
@@ -405,7 +407,7 @@ def build_environment(request, response, session, store_current=True):
"""
Build the environment dictionary into which web2py files are executed.
"""
#h,v = html,validators
# h,v = html,validators
environment = dict(_base_environment_)
if not request.env:
@@ -418,7 +420,7 @@ def build_environment(request, response, session, store_current=True):
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
]
t = environment['T'] = translator(os.path.join(request.folder,'languages'),
t = environment['T'] = translator(os.path.join(request.folder, 'languages'),
request.env.http_accept_language)
c = environment['cache'] = Cache(request)
@@ -506,10 +508,12 @@ def compile_models(folder):
save_pyc(filename)
os.unlink(filename)
def find_exposed_functions(data):
data = regex_longcomments.sub('',data)
data = regex_longcomments.sub('', data)
return regex_expose.findall(data)
def compile_controllers(folder):
"""
Compiles all the controllers in the application specified by `folder`
@@ -524,16 +528,19 @@ def compile_controllers(folder):
command = data + "\nresponse._vars=response._caller(%s)\n" % \
function
filename = pjoin(folder, 'compiled',
'controllers.%s.%s.py' % (fname[:-3],function))
'controllers.%s.%s.py' % (fname[:-3], function))
write_file(filename, command)
save_pyc(filename)
os.unlink(filename)
def model_cmp(a, b, sep='.'):
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
def model_cmp_sep(a, b, sep=os.path.sep):
return model_cmp(a,b,sep)
return model_cmp(a, b, sep)
def run_models_in(environment):
"""
@@ -544,7 +551,7 @@ def run_models_in(environment):
request = current.request
folder = request.folder
c = request.controller
#f = environment['request'].function
# f = environment['request'].function
response = current.response
path = pjoin(folder, 'models')
@@ -557,9 +564,11 @@ def run_models_in(environment):
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
else:
if compiled:
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), key=lambda f: '{0:03d}'.format(f.count('.')) + f)
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0),
key=lambda f: '{0:03d}'.format(f.count('.')) + f)
else:
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False),
key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
models_to_run = None
for model in models:
@@ -570,10 +579,10 @@ def run_models_in(environment):
if models_to_run:
if compiled:
n = len(cpath)+8
fname = model[n:-4].replace('.','/')+'.py'
fname = model[n:-4].replace('.', '/')+'.py'
else:
n = len(path)+1
fname = model[n:].replace(os.path.sep,'/')
fname = model[n:].replace(os.path.sep, '/')
if not regex.search(fname) and c != 'appadmin':
continue
elif compiled:
@@ -583,6 +592,7 @@ def run_models_in(environment):
ccode = getcfs(model, model, f)
restricted(ccode, environment, layer=model)
def run_controller_in(controller, function, environment):
"""
Runs the controller.function() (for the app specified by
@@ -596,13 +606,13 @@ def run_controller_in(controller, function, environment):
badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function)
if os.path.exists(cpath):
filename = pjoin(cpath, 'controllers.%s.%s.pyc'
% (controller, function))
if not os.path.exists(filename):
filename = pjoin(cpath, 'controllers.%s.%s.pyc' % (controller, function))
try:
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
except IOError:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
elif function == '_TEST':
# TESTING: adjust the path to include site packages
from gluon.settings import global_settings
@@ -623,15 +633,15 @@ def run_controller_in(controller, function, environment):
code += TEST_CODE
ccode = compile2(code, filename)
else:
filename = pjoin(folder, 'controllers/%s.py'
% controller)
if not os.path.exists(filename):
filename = pjoin(folder, 'controllers/%s.py' % controller)
try:
code = getcfs(filename, filename, lambda: read_file(filename))
except IOError:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badc,
web2py_error=badc)
code = getcfs(filename, filename, lambda: read_file(filename))
exposed = find_exposed_functions(code)
if not function in exposed:
if function not in exposed:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
@@ -678,7 +688,7 @@ def run_view_in(environment):
layer = 'file stream'
else:
filename = pjoin(folder, 'views', view)
if os.path.exists(cpath): # compiled views
if os.path.exists(cpath): # compiled views
x = view.replace('/', '.')
files = ['views.%s.pyc' % x]
is_compiled = os.path.exists(pjoin(cpath, files[0]))
@@ -705,16 +715,19 @@ def run_view_in(environment):
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
layer = filename
# Compile the template
ccode = parse_template(view,
pjoin(folder, 'views'),
context=environment)
# if the view is not compiled
if not layer:
# Compile the template
ccode = parse_template(view,
pjoin(folder, 'views'),
context=environment)
layer = filename
restricted(ccode, environment, layer=layer)
# parse_template saves everything in response body
return environment['response'].body.getvalue()
def remove_compiled_application(folder):
"""
Deletes the folder `compiled` containing the compiled application.
+3 -3
View File
@@ -330,7 +330,7 @@ CONTENT_TYPE = {
'.lha': 'application/x-lha',
'.lhs': 'text/x-literate-haskell',
'.lhz': 'application/x-lhz',
'.load' : 'text/html',
'.load': 'text/html',
'.log': 'text/x-log',
'.lrz': 'application/x-lrzip',
'.ltx': 'text/x-tex',
@@ -823,7 +823,7 @@ CONTENT_TYPE = {
'.xsd': 'application/xml',
'.xsl': 'application/xslt+xml',
'.xslfo': 'text/x-xslfo',
'.xslm' : 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.xslm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.xslt': 'application/xslt+xml',
'.xspf': 'application/xspf+xml',
'.xul': 'application/vnd.mozilla.xul+xml',
@@ -843,7 +843,7 @@ def contenttype(filename, default='text/plain'):
"""
Returns the Content-Type string matching extension of the given filename.
"""
filename=to_native(filename)
filename = to_native(filename)
i = filename.rfind('.')
if i >= 0:
default = CONTENT_TYPE.get(filename[i:].lower(), default)
+56 -25
View File
@@ -5,12 +5,19 @@
# license MIT/BSD/GPL
from __future__ import print_function
import re
import sys
import urllib
from gluon._compat import maketrans, urllib_quote, unicodeT, to_bytes, to_native, xrange
from gluon.utils import local_html_escape as escape
from ast import parse as ast_parse
import ast
PY2 = sys.version_info[0] == 2
if PY2:
from urllib import quote as urllib_quote
from string import maketrans
else:
from urllib.parse import quote as urllib_quote
maketrans = str.maketrans
"""
TODO: next version should use MathJax
@@ -564,6 +571,29 @@ ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def local_html_escape(data, quote=False):
"""
Works with bytes.
Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true (the default), the quotation mark
characters, both double quote (") and single quote (') characters are also
translated.
"""
if PY2:
import cgi
data = cgi.escape(data, quote)
return data.replace("'", "&#x27;") if quote else data
else:
import html
if isinstance(data, str):
return html.escape(data, quote=quote)
data = data.replace(b"&", b"&amp;") # Must be done first!
data = data.replace(b"<", b"&lt;")
data = data.replace(b">", b"&gt;")
if quote:
data = data.replace(b'"', b"&quot;")
data = data.replace(b'\'', b"&#x27;")
return data
def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':", b)
@@ -579,7 +609,7 @@ def safe_eval(node_or_string, env):
_safe_names = {'None': None, 'True': True, 'False': False}
_safe_names.update(env)
if isinstance(node_or_string, basestring):
node_or_string = ast_parse(node_or_string, mode='eval')
node_or_string = ast.parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
@@ -599,14 +629,14 @@ def safe_eval(node_or_string, env):
if node.id in _safe_names:
return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.op, (ast.Add, ast.Sub)) and \
isinstance(node.right, ast.Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left, ast.Num) and \
isinstance(node.left.n, (int, long, float)):
left = node.left.n
right = node.right.n
if isinstance(node.op, Add):
if isinstance(node.op, ast.Add):
return left + right
else:
return left - right
@@ -765,7 +795,7 @@ def render(text,
'<table><tbody><tr class="first"><td>a</td><td>b</td></tr><tr class="even"><td>c</td><td>d</td></tr></tbody></table>'
>>> render("----\\nhello world\\n----\\n")
'<blockquote>hello world</blockquote>'
'<blockquote><p>hello world</p></blockquote>'
>>> render('[[myanchor]]')
'<p><span class="anchor" id="markmin_myanchor"></span></p>'
@@ -946,7 +976,8 @@ def render(text,
if protolinks == "default":
protolinks = protolinks_simple
pp = '\n' if pretty_print else ''
text = to_native(text)
text = text if text is None or isinstance(text, str) else text.decode('utf8', 'strict')
if not (isinstance(text, str)):
text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
@@ -994,7 +1025,7 @@ def render(text,
return LINK
text = regex_link.sub(mark_link, text)
text = escape(text)
text = local_html_escape(text)
if protolinks:
text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text)
@@ -1035,7 +1066,7 @@ def render(text,
if pend and mtag == '.': # paragraph in a list:
out.append(etags.pop())
ltags.pop()
for i in xrange(lent - lev):
for i in range(lent - lev):
out.append('<' + tag + '>' + pp)
etags.append('</' + tag + '>' + pp)
lev += 1
@@ -1044,7 +1075,7 @@ def render(text,
elif lent == lev:
if tlev[-1] != tag:
# type of list is changed (ul<=>ol):
for i in xrange(ltags.count(lent)):
for i in range(ltags.count(lent)):
ltags.pop()
out.append(etags.pop())
tlev[-1] = tag
@@ -1209,7 +1240,7 @@ def render(text,
s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls,
t_id,
'\n'.join(strings[bq_begin:lineno]), pp)
render('\n'.join(strings[bq_begin:lineno])), pp)
mtag = 'q'
else:
s = '<hr />'
@@ -1322,10 +1353,10 @@ def render(text,
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
if not k:
return m.group(0)
k = escape(k)
k = local_html_escape(k)
t = t or ''
style = 'width:%s' % w if w else ''
title = ' title="%s"' % escape(a).replace(META, DISABLED_META) if a else ''
title = ' title="%s"' % local_html_escape(a).replace(META, DISABLED_META) if a else ''
p_begin = p_end = ''
if p == 'center':
p_begin = '<p style="text-align:center">'
@@ -1349,7 +1380,7 @@ def render(text,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"' % escape(t).replace(META, DISABLED_META) if t else ''
alt = ' alt="%s"' % local_html_escape(t).replace(META, DISABLED_META) if t else ''
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
@@ -1358,12 +1389,12 @@ def render(text,
if not k and not t:
return m.group(0)
t = t or ''
a = escape(a) if a else ''
a = local_html_escape(a) if a else ''
if k:
if '#' in k and ':' not in k.split('#')[0]:
# wikipage, not external url
k = k.replace('#', '#' + id_prefix)
k = escape(k)
k = local_html_escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else ''
t = render(t, {}, {}, 'br', URL, environment, latex, None,
@@ -1373,7 +1404,7 @@ def render(text,
if t == 'NEWLINE' and not a:
return '<br />' + pp
return '<span class="anchor" id="%s">%s</span>' % (
escape(id_prefix + t),
local_html_escape(id_prefix + t),
render(a, {}, {}, 'br', URL,
environment, latex, autolinks,
protolinks, class_prefix,
@@ -1399,7 +1430,7 @@ def render(text,
def expand_meta(m):
code, b, p, s = segments.pop(0)
if code is None or m.group() == DISABLED_META:
return escape(s)
return local_html_escape(s)
if b in extra:
if code[:1] == '\n':
code = code[1:]
@@ -1411,7 +1442,7 @@ def render(text,
return str(extra[b](code))
elif b == 'cite':
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
(id_prefix + d, b, d) for d in local_html_escape(code).split(',')) + ']'
elif b == 'latex':
return LATEX % urllib_quote(code)
elif b in html_colors:
@@ -1426,12 +1457,12 @@ def render(text,
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
id = ' id="%s%s"' % (id_prefix, escape(p)) if p else ''
id = ' id="%s%s"' % (id_prefix, local_html_escape(p)) if p else ''
beg = (code[:1] == '\n')
end = [None, -1][code[-1:] == '\n']
if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, local_html_escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, local_html_escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text)
+4 -1
View File
@@ -10,8 +10,11 @@ Original author: Zachary Voase
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
"""
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from StringIO import StringIO # The pure-Python StringIO supports unicode.
import re
+18 -8
View File
@@ -8,30 +8,40 @@ Created by: Ross Peoples <ross.peoples@gmail.com>
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
"""
import cssmin
import jsmin
from . import cssmin
from . import jsmin
import os
import hashlib
import re
import sys
PY2 = sys.version_info[0] == 2
if PY2:
hashlib_md5 = hashlib.md5
else:
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
def open_py23(filename, mode):
if PY2:
f = open(filename, mode + 'b')
else:
f = open(filename, mode, encoding="utf8")
return f
def read_binary_file(filename):
f = open(filename, 'rb')
f = open_py23(filename, 'r')
data = f.read()
f.close()
return data
def write_binary_file(filename, data):
f = open(filename, 'wb')
f = open_py23(filename, 'w')
f.write(data)
f.close()
def fix_links(css, static_path):
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
def minify(files, path_info, folder, optimize_css, optimize_js,
ignore_concat=[],
ignore_minify=['/jquery.js', '/anytime.js']):
@@ -109,7 +119,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
js.append(contents)
else:
js.append(filename)
dest_key = hashlib.md5(repr(processed)).hexdigest()
dest_key = hashlib_md5(repr(processed)).hexdigest()
if css and concat_css:
css = '\n\n'.join(contents for contents in css)
if not inline_css:
+8 -8
View File
@@ -14,6 +14,11 @@ from pydal import DAL as DAL
from pydal import Field
from pydal.objects import Row, Rows, Table, Query, Set, Expression
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
from gluon.serializers import custom_json, xml
from gluon.utils import web2py_uuid
from gluon import sqlhtml
from pydal.drivers import DRIVERS
def _default_validators(db, field):
"""
@@ -78,14 +83,10 @@ def _default_validators(db, field):
if (field.notnull or field.unique) and field_type not in excluded_fields:
requires.insert(0, validators.IS_NOT_EMPTY())
elif not field.notnull and not field.unique and requires:
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
requires[0] = \
validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
return requires
from gluon.serializers import custom_json, xml
from gluon.utils import web2py_uuid
from gluon import sqlhtml
DAL.serializers = {'json': custom_json, 'xml': xml}
DAL.validators_method = _default_validators
DAL.uuid = lambda x: web2py_uuid()
@@ -96,8 +97,7 @@ DAL.representers = {
DAL.Field = Field
DAL.Table = Table
#: add web2py contrib drivers to pyDAL
from pydal.drivers import DRIVERS
# add web2py contrib drivers to pyDAL
if not DRIVERS.get('pymysql'):
try:
from .contrib import pymysql
+12 -13
View File
@@ -15,13 +15,13 @@ import codecs
# None represents a potentially variable byte. "##" in the XML spec...
autodetect_dict = { # bytepattern : ("name",
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
(0xFE, 0xFF, None, None): ("utf_16_be"),
(0xFF, 0xFE, None, None): ("utf_16_le"),
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
(0xFE, 0xFF, None, None): ("utf_16_be"),
(0xFF, 0xFE, None, None): ("utf_16_le"),
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
}
@@ -36,10 +36,10 @@ def autoDetectXMLEncoding(buffer):
# buffer at once but otherwise we'd have to decode a character at
# a time looking for the quote character...that's a pain
encoding = "utf_8" # according to the XML spec, this is the default
# this code successively tries to refine the default
# whenever it fails to refine, it falls back to
# the last place encoding was set.
encoding = "utf_8"
# according to the XML spec, this is the default this code successively tries to refine the default
# whenever it fails to refine, it falls back to the last place encoding was set.
if len(buffer) >= 4:
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
enc_info = autodetect_dict.get(bytes, None)
@@ -51,8 +51,7 @@ def autoDetectXMLEncoding(buffer):
enc_info = None
if enc_info:
encoding = enc_info # we've got a guess... these are
#the new defaults
encoding = enc_info # we've got a guess... these are the new defaults
# try to find a more precise encoding using xml declaration
secret_decoder_ring = codecs.lookup(encoding)[1]
+19 -16
View File
@@ -13,7 +13,8 @@ Contains the classes for the global used variables:
- Session
"""
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, unicodeT, long
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \
unicodeT, long, hashlib_md5
from gluon.storage import Storage, List
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
from gluon.contenttype import contenttype
@@ -30,7 +31,7 @@ from gluon.fileutils import copystream
import hashlib
from pydal.contrib import portalocker
from pickle import Pickler, MARK, DICT, EMPTY_DICT
#from types import DictionaryType
# from types import DictionaryType
import datetime
import re
import os
@@ -48,7 +49,7 @@ PAST = 'Sat, 1-Jan-1971 00:00:00'
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
try:
#FIXME PY3
# FIXME PY3
from gluon.contrib.minify import minify
have_minify = True
except ImportError:
@@ -79,6 +80,7 @@ template_mapping = {
'js:inline': js_inline
}
# IMPORTANT:
# this is required so that pickled dict(s) and class.__dict__
# are sorted and web2py can detect without ambiguity when a session changes
@@ -95,6 +97,7 @@ else:
SortingPickler.dispatch_table = copyreg.dispatch_table.copy()
SortingPickler.dispatch_table[dict] = SortingPickler.save_dict
def sorting_dumps(obj, protocol=None):
file = StringIO()
SortingPickler(file, protocol).dump(obj)
@@ -120,7 +123,7 @@ def copystream_progress(request, chunk_size=10 ** 5):
dest = tempfile.NamedTemporaryFile()
except NotImplementedError: # and GAE this
dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.get_vars:
if 'X-Progress-ID' not in request.get_vars:
copystream(source, dest, size, chunk_size)
return dest
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
@@ -198,7 +201,8 @@ class Request(Storage):
"""Takes the QUERY_STRING and unpacks it to get_vars
"""
query_string = self.env.get('query_string', '')
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
dget = urlparse.parse_qs(query_string, keep_blank_values=1)
# Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
get_vars = self._get_vars = Storage(dget)
for (key, value) in iteritems(get_vars):
if isinstance(value, list) and len(value) == 1:
@@ -228,8 +232,7 @@ class Request(Storage):
body.seek(0)
# parse POST variables on POST, PUT, BOTH only in post_vars
if (body and not is_json
and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'):
query_string = env.pop('QUERY_STRING', None)
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
try:
@@ -355,7 +358,7 @@ class Request(Storage):
def f(_action=action, *a, **b):
request.is_restful = True
env = request.env
is_json = env.content_type=='application/json'
is_json = env.content_type == 'application/json'
method = env.request_method
if not ignore_extension and len(request.args) and '.' in request.args[-1]:
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
@@ -451,13 +454,13 @@ class Response(Storage):
for meta in iteritems((self.meta or {})):
k, v = meta
if isinstance(v, dict):
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), to_native(xmlescape(v[key]))) for key in v) +' />\n'
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key),
to_native(xmlescape(v[key]))) for key in v) + ' />\n'
else:
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
self.write(s, escape=False)
def include_files(self, extensions=None):
"""
Includes files (usually in the head).
Can minify and cache local files
@@ -484,8 +487,7 @@ class Response(Storage):
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
# cache for 5 minutes by default
key = hashlib.md5(repr(files)).hexdigest()
key = hashlib_md5(repr(files)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60 * 5)
def call_minify(files=files):
@@ -523,6 +525,7 @@ class Response(Storage):
tmpl = template_mapping.get(f)
if tmpl:
s.append(tmpl % item[1])
self.write(''.join(s), escape=False)
def stream(self,
@@ -578,9 +581,9 @@ class Response(Storage):
if hasattr(stream, 'name'):
filename = stream.name
if filename and not 'content-type' in keys:
if filename and 'content-type' not in keys:
headers['Content-Type'] = contenttype(filename)
if filename and not 'content-length' in keys:
if filename and 'content-length' not in keys:
try:
headers['Content-Length'] = \
os.path.getsize(filename)
@@ -1022,7 +1025,7 @@ class Session(Storage):
if self._forget:
del rcookies[response.session_id_name]
return
if self.get('httponly_cookies',True):
if self.get('httponly_cookies', True):
scookies['HttpOnly'] = True
if self._secure:
scookies['secure'] = True
@@ -1193,7 +1196,7 @@ class Session(Storage):
if (not response.session_id or
not response.session_filename or
self._forget
or self._unchanged(response)):
or self._unchanged(response)):
# self.clear_session_cookies()
return False
else:
+13 -13
View File
@@ -182,8 +182,8 @@ class Highlighter(object):
)),
'PYTHONMultilineString': (python_tokenizer,
(('ENDMULTILINESTRING',
re.compile(r'.*?("""|\'\'\')',
re.DOTALL), 'color: darkred'), )),
re.compile(r'.*?("""|\'\'\')',
re.DOTALL), 'color: darkred'), )),
'HTML': (html_tokenizer, (
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
@@ -209,7 +209,7 @@ class Highlighter(object):
mode = self.mode
while i < len(data):
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
if not token in self.suppress_tokens:
if token not in self.suppress_tokens:
match = o_re.match(data, i)
if match:
if style:
@@ -221,7 +221,7 @@ class Highlighter(object):
new_mode = \
Highlighter.all_styles[mode][0](self,
token, match, style)
if not new_mode is None:
if new_mode is not None:
mode = new_mode
i += max(1, len(match.group()))
break
@@ -241,9 +241,9 @@ class Highlighter(object):
style = self.styles[token]
if self.span_style != style:
if style != 'Keep':
if not self.span_style is None:
if self.span_style is not None:
self.output.append('</span>')
if not style is None:
if style is not None:
self.output.append('<span style="%s">' % style)
self.span_style = style
@@ -260,7 +260,7 @@ def highlight(
):
styles = styles or {}
attributes = attributes or {}
if not 'CODE' in styles:
if 'CODE' not in styles:
code_style = """
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
@@ -272,7 +272,7 @@ def highlight(
white-space: pre !important;\n"""
else:
code_style = styles['CODE']
if not 'LINENUMBERS' in styles:
if 'LINENUMBERS' not in styles:
linenumbers_style = """
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
@@ -283,7 +283,7 @@ def highlight(
color: #A0A0A0;\n"""
else:
linenumbers_style = styles['LINENUMBERS']
if not 'LINEHIGHLIGHT' in styles:
if 'LINEHIGHLIGHT' not in styles:
linehighlight_style = "background-color: #EBDDE2;"
else:
linehighlight_style = styles['LINEHIGHLIGHT']
@@ -333,8 +333,9 @@ def highlight(
== '_' and value])
if fa:
fa = ' ' + fa
return '<table%s><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
% (fa, linenumbers_style, numbers, code_style, code)
return '<table%s><tr style="vertical-align:top;">' \
'<td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td>' \
'<td><pre style="%s">%s</pre></td></tr></table>' % (fa, linenumbers_style, numbers, code_style, code)
if __name__ == '__main__':
@@ -342,5 +343,4 @@ if __name__ == '__main__':
argfp = open(sys.argv[1])
data = argfp.read()
argfp.close()
print('<html><body>' + highlight(data, sys.argv[2])\
+ '</body></html>')
print('<html><body>' + highlight(data, sys.argv[2]) + '</body></html>')
+17 -14
View File
@@ -20,7 +20,8 @@ import urllib
import base64
from gluon import sanitizer, decoder
import itertools
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
from gluon.utils import local_html_escape
import marshal
@@ -109,6 +110,7 @@ __all__ = [
DEFAULT_PASSWORD_DISPLAY = '*' * 8
def xmlescape(data, quote=True):
"""
Returns an escaped string of the provided data
@@ -124,10 +126,9 @@ def xmlescape(data, quote=True):
if not(isinstance(data, (text_type, bytes))):
# i.e., integers
data=str(data)
data = str(data)
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
# ... and do the escaping
data = local_html_escape(data, quote)
return data
@@ -671,6 +672,7 @@ def XML_pickle(data):
return XML_unpickle, (marshal.dumps(str(data)),)
copyreg.pickle(XML, XML_pickle, XML_unpickle)
@implements_bool
class DIV(XmlComponent):
"""
@@ -1309,8 +1311,10 @@ class HTML(DIV):
tag = b'html'
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
transitional = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
frameset = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
transitional = \
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
frameset = \
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
html5 = b'<!DOCTYPE HTML>\n'
def xml(self):
@@ -1861,7 +1865,7 @@ class INPUT(DIV):
except:
import traceback
print(traceback.format_exc())
msg = "Validation error, field:%s %s" % (name,validator)
msg = "Validation error, field:%s %s" % (name, validator)
raise Exception(msg)
if errors is not None:
self.vars[name] = value
@@ -1911,7 +1915,7 @@ class INPUT(DIV):
name = self.attributes.get('_name', None)
if name and hasattr(self, 'errors') \
and self.errors.get(name, None) \
and self['hideerror'] != True:
and self['hideerror'] is not True:
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
return DIV.xml(self) + DIV(
DIV(
@@ -1979,7 +1983,6 @@ class OPTGROUP(DIV):
class SELECT(INPUT):
"""
Examples:
@@ -2014,7 +2017,7 @@ class SELECT(INPUT):
if value is not None:
if not self['_multiple']:
for c in options: # my patch
if ((value is not None) and (str(c['_value']) == str(value))):
if (value is not None) and (str(c['_value']) == str(value)):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2024,7 +2027,7 @@ class SELECT(INPUT):
else:
values = [str(value)]
for c in options: # my patch
if ((value is not None) and (str(c['_value']) in values)):
if (value is not None) and (str(c['_value']) in values):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2390,7 +2393,6 @@ class FORM(DIV):
class BEAUTIFY(DIV):
"""
Turns any list, dictionary, etc into decent looking html.
@@ -2547,7 +2549,7 @@ class MENU(DIV):
li['_class'] = li['_class'] + ' ' + self['li_active']
else:
li['_class'] = self['li_active']
if len(item) <= 4 or item[4] == True:
if len(item) <= 4 or item[4] is True:
ul.append(li)
return ul
@@ -2561,7 +2563,7 @@ class MENU(DIV):
# ex: ('', False, A('title', _href=URL(...), _title="title"))
# ex: (A('title', _href=URL(...), _title="title"), False, None)
custom_items.append(item)
elif len(item) <= 4 or item[4] == True:
elif len(item) <= 4 or item[4] is True:
select.append(OPTION(CAT(prefix, item[0]),
_value=item[2], _selected=item[1]))
if len(item) > 3 and len(item[3]):
@@ -2703,7 +2705,8 @@ class web2pyHTMLParser(HTMLParser):
self.parent = self.parent.parent
except:
raise RuntimeError("unable to balance tag %s" % tagname)
if parent_tagname[:len(tagname)] == tagname: break
if parent_tagname[:len(tagname)] == tagname:
break
def markdown_serializer(text, tag=None, attr=None):
+2 -2
View File
@@ -116,7 +116,7 @@ class HTTP(Exception):
for k, v in iteritems(headers):
if isinstance(v, list):
rheaders += [(k, str(item)) for item in v]
elif not v is None:
elif v is not None:
rheaders.append((k, str(v)))
responder(status, rheaders)
if env.get('request_method', '') == 'HEAD':
@@ -148,7 +148,7 @@ class HTTP(Exception):
web2py_error=self.headers.get('web2py_error'))
def __str__(self):
"stringify me"
"""stringify me"""
return self.message
+1 -5
View File
@@ -78,13 +78,9 @@ alert_dependency = ['hashlib', 'uuid']
# Now we remove the blacklisted modules if we are using the stated
# python version.
#
# List of modules deprecated in Python 2.6 or 2.7 that are in the above set
# List of modules deprecated in Python 2.7 that are in the above list
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
if python_version >= '2.6':
base_modules += ['json', 'multiprocessing']
base_modules = list(set(base_modules).difference(set(py26_deprecated)))
if python_version >= '2.7':
base_modules += ['argparse', 'json', 'multiprocessing']
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
+20 -21
View File
@@ -11,7 +11,8 @@ The gluon wsgi application
"""
from __future__ import print_function
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
if False:
import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
import gc
import os
@@ -26,7 +27,7 @@ import random
import string
from gluon._compat import Cookie, urllib2
#from thread import allocate_lock
# from thread import allocate_lock
from gluon.fileutils import abspath, write_file
from gluon.settings import global_settings
@@ -67,14 +68,14 @@ import gluon.messageboxhandler
logging.gluon = gluon
# so we must restore it! Thanks ozancag
import locale
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
exists = os.path.exists
pjoin = os.path.join
try:
logging.config.fileConfig(abspath("logging.conf"))
except: # fails on GAE or when logfile is missing
except: # fails on GAE or when logfile is missing
logging.basicConfig()
logger = logging.getLogger("web2py")
@@ -254,6 +255,7 @@ class LazyWSGI(object):
return [data]
for item in middleware_apps:
app = item(app)
def caller(app):
return app(self.environ, self.start_response)
return lambda caller=caller, app=app: caller(app)
@@ -294,9 +296,9 @@ def wsgibase(environ, responder):
response = Response()
session = Session()
env = request.env
#env.web2py_path = global_settings.applications_parent
# env.web2py_path = global_settings.applications_parent
env.web2py_version = web2py_version
#env.update(global_settings)
# env.update(global_settings)
static_file = False
http_response = None
try:
@@ -325,7 +327,6 @@ def wsgibase(environ, responder):
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
response.stream(static_file, request=request)
# ##################################################
# fill in request items
# ##################################################
@@ -356,17 +357,15 @@ def wsgibase(environ, responder):
cmd_opts = global_settings.cmd_options
request.update(
client = client,
folder = abspath('applications', app) + os.sep,
ajax = x_req_with == 'xmlhttprequest',
cid = env.http_web2py_component_element,
is_local = (env.remote_addr in local_hosts and
client == env.remote_addr),
is_shell = False,
is_scheduler = False,
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
or env.https == 'on'
client=client,
folder=abspath('applications', app) + os.sep,
ajax=x_req_with == 'xmlhttprequest',
cid=env.http_web2py_component_element,
is_local=(env.remote_addr in local_hosts and client == env.remote_addr),
is_shell=False,
is_scheduler=False,
is_https=env.wsgi_url_scheme in HTTPS_SCHEMES or
request.env.http_x_forwarded_proto in HTTPS_SCHEMES or env.https == 'on'
)
request.url = environ['PATH_INFO']
@@ -390,7 +389,7 @@ def wsgibase(environ, responder):
% 'invalid request',
web2py_error='invalid application')
elif not request.is_local and exists(disabled):
five0three = os.path.join(request.folder,'static','503.html')
five0three = os.path.join(request.folder, 'static', '503.html')
if os.path.exists(five0three):
raise HTTP(503, file(five0three, 'r').read())
else:
@@ -406,7 +405,7 @@ def wsgibase(environ, responder):
# get the GET and POST data
# ##################################################
#parse_get_post_vars(request, environ)
# parse_get_post_vars(request, environ)
# ##################################################
# expose wsgi hooks for convenience
@@ -625,7 +624,7 @@ def appfactory(wsgiapp=wsgibase,
raise BaseException("Can't create dir %s" % profiler_dir)
filepath = pjoin(profiler_dir, 'wtest')
try:
filehandle = open( filepath, 'w' )
filehandle = open(filepath, 'w')
filehandle.close()
os.unlink(filepath)
except IOError:
+1 -2
View File
@@ -206,8 +206,7 @@ def url_out(request, environ, application, controller, function,
if host is True or (host is None and (scheme or port is not None)):
host = request.env.http_host
if not scheme or scheme is True:
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
if request else 'http'
scheme = request.env.get('wsgi_url_scheme', 'http').lower() if request else 'http'
if host:
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
url = '%s://%s%s' % (scheme, host_port, url)
+16
View File
@@ -17,6 +17,7 @@ from gluon import fileutils
from gluon.dal import DAL, Field, Table
from gluon.http import HTTP
from gluon.fileutils import open_file
from gluon.cache import CacheInRam
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
@@ -104,6 +105,21 @@ class TestAppAdmin(unittest.TestCase):
self._test_index()
remove_compiled_application(appname_path)
def test_index_minify(self):
# test for gluon/contrib/minify
self.env['response'].optimize_css = 'concat|minify'
self.env['response'].optimize_js = 'concat|minify'
self.env['current'].cache = Storage({'ram':CacheInRam()})
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
self._test_index()
file_l = os.listdir(os.path.join(appname_path, 'static', 'temp'))
file_l.sort()
self.assertTrue(len(file_l) == 2)
self.assertEqual(file_l[0][0:10], 'compressed')
self.assertEqual(file_l[1][0:10], 'compressed')
self.assertEqual(file_l[0][-3:], 'css')
self.assertEqual(file_l[1][-2:], 'js')
def test_select(self):
request = self.env['request']
request.args = List(['db'])
+56 -52
View File
@@ -1127,7 +1127,6 @@ def addrow(form, a, b, c, style, _id, position=-1):
class AuthJWT(object):
"""
Experimental!
@@ -1537,7 +1536,8 @@ class Auth(AuthAPI):
# ## these are messages that can be customized
default_messages = dict(AuthAPI.default_messages,
access_denied='Insufficient privileges',
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete the process',
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete '
'the process',
bulk_invite_subject='Invitation to join %(site)s',
delete_label='Check to delete',
email_sent='Email sent',
@@ -1840,14 +1840,15 @@ class Auth(AuthAPI):
# ## these are messages that can be customized
messages = self.messages = Messages(current.T)
messages.update(Auth.default_messages)
messages.update(ajax_failed_authentication=DIV(H4('NOT AUTHORIZED'),
'Please ',
A('login',
_href=self.settings.login_url +
('?_next=' + urllib_quote(current.request.env.http_web2py_component_location))
if current.request.env.http_web2py_component_location else ''),
' to view this content.',
_class='not-authorized alert alert-block'))
messages.update(ajax_failed_authentication=
DIV(H4('NOT AUTHORIZED'),
'Please ',
A('login',
_href=self.settings.login_url +
('?_next=' + urllib_quote(current.request.env.http_web2py_component_location))
if current.request.env.http_web2py_component_location else ''),
' to view this content.',
_class='not-authorized alert alert-block'))
messages.lock_keys = True
# for "remember me" option
@@ -1876,7 +1877,7 @@ class Auth(AuthAPI):
# _next variable in the request.
if next:
parts = next.split('/')
if not ':' in parts[0]:
if ':' not in parts[0]:
return next
elif len(parts) > 2 and parts[0].endswith(':') and parts[1:3] == ['', host]:
return next
@@ -2008,8 +2009,7 @@ class Auth(AuthAPI):
items.append({'name': T('Lost password?'),
'href': href('request_reset_password'),
'icon': 'icon-lock'})
if (self.settings.use_username and not
'retrieve_username' in self.settings.actions_disabled):
if self.settings.use_username and 'retrieve_username' not in self.settings.actions_disabled:
items.append({'name': T('Forgot username?'),
'href': href('retrieve_username'),
'icon': 'icon-edit'})
@@ -2181,9 +2181,7 @@ class Auth(AuthAPI):
current_record.replace('_', ' ').title())
for table in tables:
fieldnames = table.fields()
if ('id' in fieldnames and
'modified_on' in fieldnames and
not current_record in fieldnames):
if 'id' in fieldnames and 'modified_on' in fieldnames and current_record not in fieldnames:
table._enable_record_versioning(archive_db=archive_db,
archive_name=archive_names,
current_record=current_record,
@@ -2213,7 +2211,8 @@ class Auth(AuthAPI):
fake_migrate = db._fake_migrate
settings = self.settings
settings.enable_tokens = enable_tokens
signature_list = super(Auth, self).define_tables(username, signature, migrate, fake_migrate)._table_signature_list
signature_list = \
super(Auth, self).define_tables(username, signature, migrate, fake_migrate)._table_signature_list
now = current.request.now
reference_table_user = 'reference %s' % settings.table_user_name
@@ -2360,7 +2359,7 @@ class Auth(AuthAPI):
if callable(basic_auth_realm):
basic_auth_realm = basic_auth_realm()
elif isinstance(basic_auth_realm, (unicode, str)):
basic_realm = unicode(basic_auth_realm)
basic_realm = unicode(basic_auth_realm) # Warning python 3.5 does not have method unicod
elif basic_auth_realm is True:
basic_realm = u'' + current.request.application
http_401 = HTTP(401, u'Not Authorized', **{'WWW-Authenticate': u'Basic realm="' + basic_realm + '"'})
@@ -2462,7 +2461,7 @@ class Auth(AuthAPI):
_href=service + query_sep + "ticket=" + ticket)
else:
redirect(service + query_sep + "ticket=" + ticket)
if self.is_logged_in() and not 'renew' in request.vars:
if self.is_logged_in() and 'renew' not in request.vars:
return allow_access()
elif not self.is_logged_in() and 'gateway' in request.vars:
redirect(session._cas_service)
@@ -2779,7 +2778,7 @@ class Auth(AuthAPI):
# If auth.settings.auth_two_factor_enabled it will enable two factor
# for all the app. Another way to anble two factor is that the user
# must be part of a group that is called auth.settings.two_factor_authentication_group
if user and self.settings.auth_two_factor_enabled == True:
if user and self.settings.auth_two_factor_enabled is True:
session.auth_two_factor_enabled = True
elif user and self.settings.two_factor_authentication_group:
role = self.settings.two_factor_authentication_group
@@ -2809,7 +2808,7 @@ class Auth(AuthAPI):
# Set the way we generate the code or we send the code. For example using SMS...
two_factor_methods = self.settings.two_factor_methods
if two_factor_methods == []:
if not two_factor_methods:
# TODO: Add some error checking to handle cases where email cannot be sent
self.settings.mailer.send(
to=user.email,
@@ -2832,47 +2831,49 @@ class Auth(AuthAPI):
hideerror=settings.hideerror):
accepted_form = True
'''
"""
The lists is executed after form validation for each of the corresponding action.
For example, in your model:
In your models copy and paste:
#Before define tables, we add some extra field to auth_user
# Before define tables, we add some extra field to auth_user
auth.settings.extra_fields['auth_user'] = [
Field('motp_secret', 'password', length=512, default='', label='MOTP Secret'),
Field('motp_pin', 'string', length=128, default='', label='MOTP PIN')]
OFFSET = 60 #Be sure is the same in your OTP Client
OFFSET = 60 # Be sure is the same in your OTP Client
#Set session.auth_two_factor to None. Because the code is generated by external app.
# Set session.auth_two_factor to None. Because the code is generated by external app.
# This will avoid to use the default setting and send a code by email.
def _set_two_factor(user, auth_two_factor):
return None
def verify_otp(user, otp):
import time
from hashlib import md5
epoch_time = int(time.time())
time_start = int(str(epoch_time - OFFSET)[:-1])
time_end = int(str(epoch_time + OFFSET)[:-1])
for t in range(time_start - 1, time_end + 1):
to_hash = str(t) + user.motp_secret + user.motp_pin
hash = md5(to_hash).hexdigest()[:6]
if otp == hash:
return hash
import time
from hashlib import md5
epoch_time = int(time.time())
time_start = int(str(epoch_time - OFFSET)[:-1])
time_end = int(str(epoch_time + OFFSET)[:-1])
for t in range(time_start - 1, time_end + 1):
to_hash = str(t) + user.motp_secret + user.motp_pin
hash = md5(to_hash).hexdigest()[:6]
if otp == hash:
return hash
auth.settings.auth_two_factor_enabled = True
auth.messages.two_factor_comment = "Verify your OTP Client for the code."
auth.settings.two_factor_methods = [lambda user, auth_two_factor: _set_two_factor(user, auth_two_factor)]
auth.settings.two_factor_methods = [lambda user,
auth_two_factor: _set_two_factor(user, auth_two_factor)]
auth.settings.two_factor_onvalidation = [lambda user, otp: verify_otp(user, otp)]
'''
if self.settings.two_factor_onvalidation != []:
"""
if self.settings.two_factor_onvalidation:
for two_factor_onvalidation in self.settings.two_factor_onvalidation:
try:
session.auth_two_factor = two_factor_onvalidation(session.auth_two_factor_user, form.vars['authentication_code'])
session.auth_two_factor = \
two_factor_onvalidation(session.auth_two_factor_user, form.vars['authentication_code'])
except:
pass
else:
@@ -4146,7 +4147,7 @@ class Auth(AuthAPI):
archive_table = table._db[archive_table_name]
new_record = {current_record: form.vars.id}
for fieldname in archive_table.fields:
if not fieldname in ['id', current_record]:
if fieldname not in ['id', current_record]:
if archive_current and fieldname in form.vars:
new_record[fieldname] = form.vars[fieldname]
elif form.record and fieldname in form.record:
@@ -4956,7 +4957,10 @@ class Service(object):
Then call it with:
wget --post-data '{"jsonrpc": "2.0", "id": 1, "method": "myfunction", "params": {"a": 1, "b": 2}}' http://..../app/default/call/jsonrpc2
wget --post-data '{"jsonrpc": "2.0",
"id": 1,
"method": "myfunction",
"params": {"a": 1, "b": 2}}' http://..../app/default/call/jsonrpc2
"""
self.jsonrpc2_procedures[f.__name__] = f
@@ -5600,12 +5604,12 @@ class PluginManager(object):
where the plugin is used::
>>> print plugins.me.param1
>>> print(plugins.me.param1)
3
>>> print plugins.me.param2
>>> print(plugins.me.param2)
6
>>> plugins.me.param3 = 8
>>> print plugins.me.param3
>>> print(plugins.me.param3)
8
Here are some tests::
@@ -5613,25 +5617,25 @@ class PluginManager(object):
>>> a=PluginManager()
>>> a.x=6
>>> b=PluginManager('check')
>>> print b.x
>>> print(b.x)
6
>>> b=PluginManager() # reset settings
>>> print b.x
>>> print(b.x)
<Storage {}>
>>> b.x=7
>>> print a.x
>>> print(a.x)
7
>>> a.y.z=8
>>> print b.y.z
>>> print(b.y.z)
8
>>> test_thread_separation()
5
>>> plugins=PluginManager('me',db='mydb')
>>> print plugins.me.db
>>> print(plugins.me.db)
mydb
>>> print 'me' in plugins
>>> print('me' in plugins)
True
>>> print plugins.me.installed
>>> print(plugins.me.installed)
True
"""
@@ -5780,7 +5784,7 @@ class Expose(object):
return os.path.realpath(f)
def issymlink_out(self, f):
"True if f is a symlink and is pointing outside of self.base"
"""True if f is a symlink and is pointing outside of self.base"""
return os.path.islink(f) and not self.in_base(f)
@staticmethod
+22 -17
View File
@@ -21,7 +21,8 @@ import struct
import decimal
import unicodedata
from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, to_unicode, to_native, string_types, urlparse
from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, \
to_unicode, to_native, string_types, urlparse
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
from pydal.objects import Field, FieldVirtual, FieldMethod
from functools import reduce
@@ -452,10 +453,10 @@ class IS_IN_SET(Validator):
if not self.labels:
items = [(k, k) for (i, k) in enumerate(self.theset)]
else:
items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
items = [(k, list(self.labels)[i]) for (i, k) in enumerate(self.theset)]
if self.sort:
items.sort(key=lambda o: str(o[1]).upper())
if zero and not self.zero is None and not self.multiple:
if zero and self.zero is not None and not self.multiple:
items.insert(0, ('', self.zero))
return items
@@ -823,7 +824,7 @@ class IS_INT_IN_RANGE(Validator):
def str2dec(number):
s = str(number)
if not '.' in s:
if '.' not in s:
s += '.00'
else:
s += '0' * (2 - len(s.split('.')[1]))
@@ -1213,7 +1214,7 @@ class IS_EMAIL(Validator):
domain_encoded = to_unicode(domain).encode('idna').decode('ascii')
match_domain = self.domain_regex.match(domain_encoded)
match = (match_body != None) and (match_domain != None)
match = (match_body is not None) and (match_domain is not None)
except (TypeError, UnicodeError):
# Value may not be a string where we can look for matches.
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
@@ -1247,7 +1248,7 @@ class IS_LIST_OF_EMAILS(object):
f = IS_EMAIL()
for email in self.split_emails.findall(value):
error = f(email)[1]
if error and not email in bad_emails:
if error and email not in bad_emails:
bad_emails.append(email)
if not bad_emails:
return (value, None)
@@ -1461,9 +1462,9 @@ def unicode_to_ascii_authority(authority):
if label:
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
else:
# encodings.idna.ToASCII does not accept an empty string, but
# it is necessary for us to allow for empty labels so that we
# don't modify the URL
# encodings.idna.ToASCII does not accept an empty string, but
# it is necessary for us to allow for empty labels so that we
# don't modify the URL
asciiLabels.append('')
# RFC 3490, Section 4, Step 5
return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
@@ -1527,13 +1528,17 @@ def unicode_to_ascii_url(url, prepend_scheme):
if prepended:
scheme = ''
unparsed = urlparse.urlunparse((scheme, unicode_to_ascii_authority(authority), escape_unicode(path), '', escape_unicode(query), str(fragment)))
unparsed = urlparse.urlunparse((scheme,
unicode_to_ascii_authority(authority),
escape_unicode(path),
'',
escape_unicode(query),
str(fragment)))
if unparsed.startswith('//'):
unparsed = unparsed[2:] # Remove the // urlunparse puts in the beginning
unparsed = unparsed[2:] # Remove the // urlunparse puts in the beginning
return unparsed
class IS_GENERIC_URL(Validator):
"""
Rejects a URL string if any of the following is true:
@@ -2622,7 +2627,7 @@ class ANY_OF(Validator):
def __call__(self, value):
for validator in self.subs:
value, error = validator(value)
if error == None:
if error is None:
break
return value, error
@@ -2762,7 +2767,7 @@ class LazyCrypt(object):
else:
digest_alg, key = self.crypt.digest_alg, ''
if self.crypt.salt:
if self.crypt.salt == True:
if self.crypt.salt:
salt = str(web2py_uuid()).replace('-', '')[-16:]
else:
salt = self.crypt.salt
@@ -2847,7 +2852,7 @@ class CRYPT(object):
Supports standard algorithms
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
... print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
... print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
md5$...$...
sha1$...$...
sha256$...$...
@@ -2859,13 +2864,13 @@ class CRYPT(object):
Supports for pbkdf2
>>> alg = 'pbkdf2(1000,20,sha512)'
>>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
>>> print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
pbkdf2(1000,20,sha512)$...$...
An optional hmac_key can be specified and it is used as salt prefix
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
>>> print a
>>> print(a)
md5$...$...
Even if the algorithm changes the hash can still be validated