diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..b7353733 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..066b2d92 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.travis.yml b/.travis.yml index e7c88e7e..644e6211 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,12 +10,16 @@ python: - '2.7' - '3.5' - '3.6' + - '3.6-dev' + - '3.7-dev' - 'pypy-5.3.1' - 'pypy3.5-5.7.1-beta' matrix: allow_failures: - python: 'pypy3.5-5.7.1-beta' + - python: '3.6-dev' + - python: '3.7-dev' install: - pip install -e . diff --git a/CHANGELOG b/CHANGELOG index 3aa5d351..4869fb15 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,15 @@ -## 2.15.0b1 +## 2.17.1 +- pydal 18.08 +- many small bug fixes + +## 2.16.1 +- pydal 17.11 +- bootstrap 4 +- better welcome examples +- many bug fixes + +## 2.15.1-4 +- pydal 17.08 - dropped support for python 2.6 - dropped web shell - experimental python 3 support diff --git a/Makefile b/Makefile index 1f6e9099..870c51f9 100644 --- a/Makefile +++ b/Makefile @@ -30,11 +30,7 @@ update: wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py echo "remember that pymysql was tweaked" -src: - ### Use semantic versioning - echo 'Version 2.15.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION - ### rm -f all junk files - make clean +rmfiles: ### clean up baisc apps rm -f routes.py rm -rf applications/*/sessions/* @@ -46,6 +42,12 @@ src: rm -rf applications/admin/uploads/* rm -rf applications/welcome/uploads/* rm -rf applications/examples/uploads/* +src: + ### Use semantic versioning + echo 'Version 2.17.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION + ### rm -f all junk files + #make clean + # make rmfiles ### make welcome layout and appadmin the default cp applications/welcome/views/appadmin.html applications/admin/views cp applications/welcome/views/appadmin.html applications/examples/views @@ -54,7 +56,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/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 + cd ..; zip -r --exclude=**.git** 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 @@ -97,6 +99,8 @@ win: cp -r applications/welcome ../web2py_win/web2py/applications cp -r applications/examples ../web2py_win/web2py/applications cp applications/__init__.py ../web2py_win/web2py/applications + # per https://github.com/web2py/web2py/issues/1716 + mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done' cd ../web2py_win; zip -r web2py_win.zip web2py mv ../web2py_win/web2py_win.zip . run: diff --git a/VERSION b/VERSION index 09af535e..5cb3e1ea 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.15.2-stable+timestamp.2017.07.19.01.21.31 +Version 2.17.1-stable+timestamp.2018.08.05.17.57.00 diff --git a/anyserver.py b/anyserver.py index a500922a..b935a1ed 100644 --- a/anyserver.py +++ b/anyserver.py @@ -43,8 +43,8 @@ class Servers: @staticmethod def cherrypy(app, address, **options): - from cherrypy import wsgiserver - server = wsgiserver.CherryPyWSGIServer(address, app) + from cheroot.wsgi import Server as WSGIServer + server = WSGIServer(address, app) server.start() @staticmethod diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py index 139ad1aa..da050a5b 100644 --- a/applications/admin/controllers/appadmin.py +++ b/applications/admin/controllers/appadmin.py @@ -224,15 +224,15 @@ def select(): session.last_orderby = orderby session.last_query = request.vars.query form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', - _name='query', _value=request.vars.query or '', + _name='query', _value=request.vars.query or '', _class="form-control", requires=IS_NOT_EMPTY( error_message=T("Cannot be empty")))), TR(T('Update:'), INPUT(_name='update_check', _type='checkbox', value=False), INPUT(_style='width:400px', _name='update_fields', _value=request.vars.update_fields - or '')), TR(T('Delete:'), INPUT(_name='delete_check', + or '', _class="form-control")), TR(T('Delete:'), INPUT(_name='delete_check', _class='delete', _type='checkbox', value=False), ''), - TR('', '', INPUT(_type='submit', _value=T('submit')))), + TR('', '', INPUT(_type='submit', _value=T('submit'), _class="btn btn-primary"))), _action=URL(r=request, args=request.args)) tb = None @@ -274,7 +274,7 @@ def select(): formcsv = FORM(str(T('or import from csv file')) + " ", INPUT(_type='file', _name='csvfile'), INPUT(_type='hidden', _value=csv_table, _name='table'), - INPUT(_type='submit', _value=T('import'))) + INPUT(_type='submit', _value=T('import'), _class="btn btn-primary")) else: formcsv = None if formcsv and formcsv.process().accepted: @@ -394,10 +394,9 @@ def ccache(): redirect(URL(r=request)) try: - from guppy import hpy - hp = hpy() + from pympler.asizeof import asizeof except ImportError: - hp = False + asizeof = False import shelve import os @@ -451,9 +450,9 @@ def ccache(): ram['ratio'] = 0 for key, value in iteritems(cache.ram.storage): - if hp: - ram['bytes'] += hp.iso(value[1]).size - ram['objects'] += hp.iso(value[1]).count + if asizeof: + ram['bytes'] += asizeof(value[1]) + ram['objects'] += 1 ram['entries'] += 1 if value[0] < ram['oldest']: ram['oldest'] = value[0] @@ -469,9 +468,9 @@ def ccache(): except (KeyError, ZeroDivisionError): disk['ratio'] = 0 else: - if hp: - disk['bytes'] += hp.iso(value[1]).size - disk['objects'] += hp.iso(value[1]).count + if asizeof: + disk['bytes'] += asizeof(value[1]) + disk['objects'] += 1 disk['entries'] += 1 if value[0] < disk['oldest']: disk['oldest'] = value[0] @@ -511,7 +510,7 @@ def ccache(): total['keys'] = key_table(total['keys']) return dict(form=form, total=total, - ram=ram, disk=disk, object_stats=hp != False) + ram=ram, disk=disk, object_stats=asizeof != False) def table_template(table): @@ -589,7 +588,7 @@ def manage(): auth.table_permission().group_id.label = T('Role') auth.table_permission().name.label = T('Permission') if table == auth.table_user(): - linked_tables=[auth.settings.table_membership_name] + linked_tables = [auth.settings.table_membership_name] elif table == auth.table_group(): orderby = 'role' if not request.args(3) or '.group_id' not in request.args(3) else None elif table == auth.table_permission(): @@ -605,13 +604,13 @@ def manage(): def hooks(): import functools import inspect - list_op=['_%s_%s' %(h,m) for h in ['before', 'after'] for m in ['insert','update','delete']] - tables=[] - with_build_it=False + list_op = ['_%s_%s' %(h,m) for h in ['before', 'after'] for m in ['insert','update','delete']] + tables = [] + with_build_it = False for db_str in sorted(databases): db = databases[db_str] for t in db.tables: - method_hooks=[] + method_hooks = [] for op in list_op: functions = [] for f in getattr(db[t], op): @@ -631,16 +630,16 @@ def hooks(): except: pass if len(functions): - method_hooks.append({'name':op, 'functions':functions}) + method_hooks.append({'name': op, 'functions':functions}) if len(method_hooks): - tables.append({'name':"%s.%s" % (db_str,t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks}) + tables.append({'name': "%s.%s" % (db_str, t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks}) # Render ul_main = UL(_class='nav nav-list') for t in tables: ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug'])) ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none') for op in t['method_hooks']: - ul_t.append(LI (op['name'])) + ul_t.append(LI(op['name'])) ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']])) ul_main.append(ul_t) return ul_main @@ -653,41 +652,40 @@ def hooks(): def d3_graph_model(): """ See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha and also the app_admin bg_graph_model function - + Create a list of table dicts, called "nodes" """ - - data = {} + nodes = [] links = [] - subgraphs = dict() + for database in databases: + db = eval_in_global_env(database) + for tablename in db.tables: + fields = [] + for field in db[tablename]: + f_type = field.type + if not isinstance(f_type, str): + disp = ' ' + elif f_type == 'string': + disp = field.length + elif f_type == 'id': + disp = "PK" + elif f_type.startswith('reference') or \ + f_type.startswith('list:reference'): + disp = "FK" + else: + disp = ' ' + fields.append(dict(name=field.name, type=field.type, disp=disp)) - for tablename in db.tables: - fields = [] - for field in db[tablename]: - f_type = field.type - if not isinstance(f_type,str): - disp = ' ' - elif f_type == 'string': - disp = field.length - elif f_type == 'id': - disp = "PK" - elif f_type.startswith('reference') or \ - f_type.startswith('list:reference'): - disp = "FK" - else: - disp = ' ' - fields.append(dict(name= field.name, type=field.type, disp = disp)) + if isinstance(f_type, str) and ( + f_type.startswith('reference') or + f_type.startswith('list:reference')): + referenced_table = f_type.split()[1].split('.')[0] - if isinstance(f_type,str) and ( - f_type.startswith('reference') or - f_type.startswith('list:reference')): - referenced_table = f_type.split()[1].split('.')[0] + links.append(dict(source=tablename, target = referenced_table)) - links.append(dict(source=tablename, target = referenced_table)) - - nodes.append(dict(name=tablename, type="table", fields = fields)) + 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('admin','static','js/d3.min.js')) diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index ff4ac4a2..28198d2a 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -963,8 +963,7 @@ def edit_language(): form = SPAN(strings['__corrupted__'], _class='error') return dict(filename=filename, form=form) - keys = sorted(strings.keys(), lambda x, y: cmp( - unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())) + keys = sorted(strings.keys(), key=lambda x: to_native(x).lower()) rows = [] rows.append(H2(T('Original/Translation'))) diff --git a/applications/admin/controllers/pythonanywhere.py b/applications/admin/controllers/pythonanywhere.py index e61b06ab..d9989cee 100644 --- a/applications/admin/controllers/pythonanywhere.py +++ b/applications/admin/controllers/pythonanywhere.py @@ -5,7 +5,7 @@ import re import gzip import tarfile from gluon.contrib.simplejsonrpc import ServerProxy -from gluon._compat import StringIO, ProtocolError +from gluon._compat import StringIO, ProtocolError, urlencode, urllib2 def deploy(): response.title = T('Deploy to pythonanywhere') @@ -26,9 +26,8 @@ def create_account(): except ProtocolError as error: pass - import urllib, urllib2 url = 'https://www.pythonanywhere.com/api/web2py/create_account' - data = urllib.urlencode(request.vars) + data = urlencode(request.vars) req = urllib2.Request(url, data) try: diff --git a/applications/admin/static/js/jquery.js b/applications/admin/static/js/jquery.js index 4024b662..644d35e2 100644 --- a/applications/admin/static/js/jquery.js +++ b/applications/admin/static/js/jquery.js @@ -1,4 +1,4 @@ -/*! 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 RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;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},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(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[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; -}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",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_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="",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);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("
') -# -# # class TestRecaptcha2(unittest.TestCase): # def test_Recaptcha2(self): # from html import FORM @@ -248,7 +240,6 @@ class TestAuthJWT(unittest.TestCase): self.user_data['password'])[0])) self.jwtauth = AuthJWT(self.auth, secret_key='secret', verify_expiration=True) - def test_jwt_token_manager(self): import gluon.serializers self.request.vars.update(self.user_data) @@ -260,7 +251,6 @@ class TestAuthJWT(unittest.TestCase): self.token = self.jwtauth.jwt_token_manager() self.assertIsNotNone(self.token) - def test_allows_jwt(self): import gluon.serializers self.request.vars.update(self.user_data) @@ -270,11 +260,13 @@ class TestAuthJWT(unittest.TestCase): del self.request.vars['password'] self.token = self.jwtauth.jwt_token_manager() self.request.vars._token = gluon.serializers.json_parser.loads(self.token)['token'] + @self.jwtauth.allows_jwt() def optional_auth(): self.assertEqual(self.user_data['username'], self.auth.user.username) optional_auth() + @unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'") # class TestAuth(unittest.TestCase): # @@ -495,8 +487,6 @@ class TestAuthJWT(unittest.TestCase): # # impersonate_form = auth.impersonate(user_id=omer_id) # # self.assertTrue(auth.is_impersonating()) # # self.assertEqual(impersonate_form, 'test') - - class TestAuth(unittest.TestCase): def myassertRaisesRegex(self, *args, **kwargs): @@ -904,7 +894,7 @@ class TestAuth(unittest.TestCase): self.assertEqual(count_log_event_test_after, count_log_event_test_before) def test_add_membership(self): - user = self.db(self.db.auth_user.username == 'bart').select().first() # bypass login_bare() + user = self.db(self.db.auth_user.username == 'bart').select().first() # bypass login_bare() user_id = user.id role_name = 'test_add_membership_group' group_id = self.auth.add_group(role_name) @@ -1174,6 +1164,7 @@ class TestToolsFunctions(unittest.TestCase): pjoin = os.path.join + def have_symlinks(): return os.name == 'posix' @@ -1181,18 +1172,20 @@ def have_symlinks(): class Test_Expose__in_base(unittest.TestCase): def test_in_base(self): - are_under = [ # (sub, base) + are_under = [ + # (sub, base) ('/foo/bar', '/foo'), ('/foo', '/foo'), ('/foo', '/'), ('/', '/'), ] for sub, base in are_under: - self.assertTrue( Expose._Expose__in_base(subdir=sub, basedir=base, sep='/'), - '%s is not under %s' % (sub, base) ) + self.assertTrue(Expose._Expose__in_base(subdir=sub, basedir=base, sep='/'), + '%s is not under %s' % (sub, base)) def test_not_in_base(self): - are_not_under = [ # (sub, base) + are_not_under = [ + # (sub, base) ('/foobar', '/foo'), ('/foo', '/foo/bar'), ('/bar', '/foo'), @@ -1200,8 +1193,8 @@ class Test_Expose__in_base(unittest.TestCase): ('/', '/x'), ] for sub, base in are_not_under: - self.assertFalse( Expose._Expose__in_base(subdir=sub, basedir=base, sep='/'), - '%s should not be under %s' % (sub, base) ) + self.assertFalse(Expose._Expose__in_base(subdir=sub, basedir=base, sep='/'), + '%s should not be under %s' % (sub, base)) class TestExpose(unittest.TestCase): @@ -1237,7 +1230,7 @@ class TestExpose(unittest.TestCase): shutil.rmtree(self.base_dir) def make_dirs(self): - """setup direcotry strucutre""" + """setup directory structure""" for d in (['inside'], ['inside', 'dir1'], ['inside', 'dir2'], @@ -1257,7 +1250,7 @@ class TestExpose(unittest.TestCase): f.write('README content') def make_symlinks(self): - """setup extenstion for posix systems""" + """setup extension for posix systems""" # inside links os.symlink( pjoin(self.base_dir, 'inside', 'dir1'), diff --git a/gluon/tests/test_validators.py b/gluon/tests/test_validators.py index e534fbd8..73712664 100644 --- a/gluon/tests/test_validators.py +++ b/gluon/tests/test_validators.py @@ -278,8 +278,68 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, ('jerry', 'oops')) rtn = IS_IN_DB(db, 'person.id', '%(name)s', auto_add=True)('jerry') self.assertEqual(rtn, (3, None)) + # Test it works with reference table + db.define_table('ref_table', + Field('name'), + Field('person_id', 'reference person') + ) + ret = db.ref_table.validate_and_insert(name='test reference table') + self.assertFalse(list(ret.errors)) + ret = db.ref_table.validate_and_insert(name='test reference table', person_id=george_id) + self.assertFalse(list(ret.errors)) + rtn = IS_IN_DB(db, 'ref_table.person_id', '%(name)s')(george_id) + self.assertEqual(rtn, (george_id, None)) + # Test it works with reference table.field and keyed table + db.define_table('person_keyed', + Field('name'), + primarykey=['name']) + db.person_keyed.insert(name='george') + db.person_keyed.insert(name='costanza') + rtn = IS_IN_DB(db, 'person_keyed.name')('george') + self.assertEqual(rtn, ('george', None)) + db.define_table('ref_table_field', + Field('name'), + Field('person_name', 'reference person_keyed.name') + ) + ret = db.ref_table_field.validate_and_insert(name='test reference table.field') + self.assertFalse(list(ret.errors)) + ret = db.ref_table_field.validate_and_insert(name='test reference table.field', person_name='george') + self.assertFalse(list(ret.errors)) + vldtr = IS_IN_DB(db, 'ref_table_field.person_name', '%(name)s') + vldtr.options() + rtn = vldtr('george') + self.assertEqual(rtn, ('george', None)) + # Test it works with list:reference table + db.define_table('list_ref_table', + Field('name'), + Field('person_list', 'list:reference person')) + ret = db.list_ref_table.validate_and_insert(name='test list:reference table') + self.assertFalse(list(ret.errors)) + ret = db.list_ref_table.validate_and_insert(name='test list:reference table', person_list=[george_id,costanza_id]) + self.assertFalse(list(ret.errors)) + vldtr = IS_IN_DB(db, 'list_ref_table.person_list') + vldtr.options() + rtn = vldtr([george_id,costanza_id]) + self.assertEqual(rtn, ([george_id,costanza_id], None)) + # Test it works with list:reference table.field and keyed table + #db.define_table('list_ref_table_field', + # Field('name'), + # Field('person_list', 'list:reference person_keyed.name')) + #ret = db.list_ref_table_field.validate_and_insert(name='test list:reference table.field') + #self.assertFalse(list(ret.errors)) + #ret = db.list_ref_table_field.validate_and_insert(name='test list:reference table.field', person_list=['george','costanza']) + #self.assertFalse(list(ret.errors)) + #vldtr = IS_IN_DB(db, 'list_ref_table_field.person_list') + #vldtr.options() + #rtn = vldtr(['george','costanza']) + #self.assertEqual(rtn, (['george','costanza'], None)) db.person.drop() db.category.drop() + db.person_keyed.drop() + db.ref_table.drop() + db.ref_table_field.drop() + db.list_ref_table.drop() + #db.list_ref_table_field.drop() def test_IS_NOT_IN_DB(self): from gluon.dal import DAL, Field @@ -444,6 +504,8 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, (None, 'Enter a value')) rtn = IS_NOT_EMPTY()('') self.assertEqual(rtn, ('', 'Enter a value')) + rtn = IS_NOT_EMPTY()(b'') + self.assertEqual(rtn, (b'', 'Enter a value')) rtn = IS_NOT_EMPTY()(' ') self.assertEqual(rtn, (' ', 'Enter a value')) rtn = IS_NOT_EMPTY()(' \n\t') @@ -703,12 +765,12 @@ class TestValidators(unittest.TestCase): rtn = IS_LIST_OF(IS_INT_IN_RANGE(0, 10))(1) self.assertEqual(rtn, ([1], None)) rtn = IS_LIST_OF(IS_INT_IN_RANGE(0, 10), minimum=10)([1, 2]) - self.assertEqual(rtn, ([1, 2], 'Enter between 10 and 100 values')) + self.assertEqual(rtn, ([1, 2], 'Minimum length is 10')) rtn = IS_LIST_OF(IS_INT_IN_RANGE(0, 10), maximum=2)([1, 2, 3]) - self.assertEqual(rtn, ([1, 2, 3], 'Enter between 0 and 2 values')) + self.assertEqual(rtn, ([1, 2, 3], 'Maximum length is 2')) # regression test for issue 742 rtn = IS_LIST_OF(minimum=1)('') - self.assertEqual(rtn, ([], 'Enter between 1 and 100 values')) + self.assertEqual(rtn, ([], 'Minimum length is 1')) def test_IS_LOWER(self): rtn = IS_LOWER()('ABC') @@ -799,6 +861,10 @@ class TestValidators(unittest.TestCase): self.assertEqual(rtn, [('', ''), ('id1', 'first label'), ('id2', 'second label')]) rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options() self.assertEqual(rtn, [('', 'zero'), ('id1', 'first label'), ('id2', 'second label')]) + rtn = IS_EMPTY_OR((IS_LOWER(), IS_EMAIL()))('AAA') + self.assertEqual(rtn, ('aaa', 'Enter a valid email address')) + rtn = IS_EMPTY_OR([IS_LOWER(), IS_EMAIL()])('AAA') + self.assertEqual(rtn, ('aaa', 'Enter a valid email address')) def test_CLEANUP(self): rtn = CLEANUP()('helloò') diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py index c8f5be29..a8d41a1d 100644 --- a/gluon/tests/test_web.py +++ b/gluon/tests/test_web.py @@ -102,12 +102,12 @@ class TestWeb(LiveTest): password='test', _formname='login') client.post('user/login', data=data) - self.assertTrue('Welcome Homer' in client.text) + self.assertTrue('Homer' in client.text) # check registration and login were successful client.get('index') - self.assertTrue('Welcome Homer' in client.text) + self.assertTrue('Homer' in client.text) client = WebClient('http://127.0.0.1:8000/admin/default/') client.post('index', data=dict(password='testpass')) diff --git a/gluon/tools.py b/gluon/tools.py index 0cf9b205..5cc4bf40 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -12,7 +12,7 @@ Auth, Mail, PluginManager and various utilities import base64 from functools import reduce -from gluon._compat import pickle, thread, urllib2, Cookie, StringIO +from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, urlencode from gluon._compat import configparser, MIMEBase, MIMEMultipart, MIMEText, Header from gluon._compat import Encoders, Charset, long, urllib_quote, iteritems from gluon._compat import to_bytes, to_native, add_charset @@ -27,7 +27,6 @@ import time import fnmatch import traceback import smtplib -import urllib import email.utils import random import hmac @@ -53,7 +52,7 @@ import gluon.serializers as serializers Table = DAL.Table Field = DAL.Field -__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki', +__all__ = ['Mail', 'Auth', 'Recaptcha2', 'Crud', 'Service', 'Wiki', 'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate'] # mind there are two loggers here (logger and crud.settings.logger)! @@ -776,7 +775,9 @@ class Mail(object): if attachments: result = mail.send_mail( sender=sender, to=origTo, - subject=to_unicode(subject, encoding), body=to_unicode(text or '', encoding), html=html, + subject=to_unicode(subject, encoding), + body=to_unicode(text or '', encoding), + html=html, attachments=attachments, **xcc) elif html and (not raw): result = mail.send_mail( @@ -786,6 +787,20 @@ class Mail(object): result = mail.send_mail( sender=sender, to=origTo, subject=to_unicode(subject, encoding), body=to_unicode(text or '', encoding), **xcc) + elif self.settings.server == 'aws': + import boto3 + from botocore.exceptions import ClientError + client = boto3.client('ses') + try: + raw = {'Data': payload.as_string()} + response = client.send_raw_email(RawMessage=raw, + Source=sender, + Destinations=to) + return True + except ClientError as e: + # we should log this error: + # print e.response['Error']['Message'] + return False else: smtp_args = self.settings.server.split(':') kwargs = dict(timeout=self.settings.timeout) @@ -811,149 +826,6 @@ class Mail(object): return True -class Recaptcha(DIV): - - """ - Examples: - Use as:: - - form = FORM(Recaptcha(public_key='...', private_key='...')) - - or:: - - form = SQLFORM(...) - form.append(Recaptcha(public_key='...', private_key='...')) - - """ - - API_SSL_SERVER = 'https://www.google.com/recaptcha/api' - API_SERVER = 'http://www.google.com/recaptcha/api' - VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify' - - def __init__(self, - request=None, - public_key='', - private_key='', - use_ssl=False, - error=None, - error_message='invalid', - label='Verify:', - options='', - comment='', - ajax=False - ): - request = request or current.request - self.request_vars = request and request.vars or current.request.vars - self.remote_addr = request.env.remote_addr - self.public_key = public_key - self.private_key = private_key - self.use_ssl = use_ssl - self.error = error - self.errors = Storage() - self.error_message = error_message - self.components = [] - self.attributes = {} - self.label = label - self.options = options - self.comment = comment - self.ajax = ajax - - def _validate(self): - - # for local testing: - - recaptcha_challenge_field = \ - self.request_vars.recaptcha_challenge_field - recaptcha_response_field = \ - self.request_vars.recaptcha_response_field - private_key = self.private_key - remoteip = self.remote_addr - if not (recaptcha_response_field and recaptcha_challenge_field - and len(recaptcha_response_field) - and len(recaptcha_challenge_field)): - self.errors['captcha'] = self.error_message - return False - params = urllib.urlencode({ - 'privatekey': private_key, - 'remoteip': remoteip, - 'challenge': recaptcha_challenge_field, - 'response': recaptcha_response_field, - }) - request = urllib2.Request( - url=self.VERIFY_SERVER, - data=params, - headers={'Content-type': 'application/x-www-form-urlencoded', - 'User-agent': 'reCAPTCHA Python'}) - httpresp = urllib2.urlopen(request) - return_values = httpresp.read().splitlines() - httpresp.close() - return_code = return_values[0] - if return_code == 'true': - del self.request_vars.recaptcha_challenge_field - del self.request_vars.recaptcha_response_field - self.request_vars.captcha = '' - return True - else: - # In case we get an error code, store it so we can get an error message - # from the /api/challenge URL as described in the reCAPTCHA api docs. - self.error = return_values[1] - self.errors['captcha'] = self.error_message - return False - - def xml(self): - public_key = self.public_key - use_ssl = self.use_ssl - error_param = '' - if self.error: - error_param = '&error=%s' % self.error - if use_ssl: - server = self.API_SSL_SERVER - else: - server = self.API_SERVER - if not self.ajax: - captcha = DIV( - SCRIPT("var RecaptchaOptions = {%s};" % self.options), - SCRIPT(_type="text/javascript", - _src="%s/challenge?k=%s%s" % (server, public_key, error_param)), - TAG.noscript( - IFRAME( - _src="%s/noscript?k=%s%s" % ( - server, public_key, error_param), - _height="300", _width="500", _frameborder="0"), BR(), - INPUT( - _type='hidden', _name='recaptcha_response_field', - _value='manual_challenge')), _id='recaptcha') - - else: # use Google's ajax interface, needed for LOADed components - - url_recaptcha_js = "%s/js/recaptcha_ajax.js" % server - RecaptchaOptions = "var RecaptchaOptions = {%s}" % self.options - script = """%(options)s; - jQuery.getScript('%(url)s',function() { - Recaptcha.create('%(public_key)s', - 'recaptcha',jQuery.extend(RecaptchaOptions,{'callback':Recaptcha.focus_response_field})) - }) """ % ({'options': RecaptchaOptions, 'url': url_recaptcha_js, 'public_key': public_key}) - captcha = DIV( - SCRIPT( - script, - _type="text/javascript", - ), - TAG.noscript( - IFRAME( - _src="%s/noscript?k=%s%s" % ( - server, public_key, error_param), - _height="300", _width="500", _frameborder="0"), BR(), - INPUT( - _type='hidden', _name='recaptcha_response_field', - _value='manual_challenge')), _id='recaptcha') - - if not self.errors.captcha: - return XML(captcha).xml() - else: - captcha.append(DIV(self.errors['captcha'], _class='error')) - return XML(captcha).xml() - - class Recaptcha2(DIV): """ Experimental: @@ -1026,7 +898,7 @@ class Recaptcha2(DIV): if not recaptcha_response_field: self.errors['captcha'] = self.error_message return False - params = urllib.urlencode({ + params = urlencode({ 'secret': self.private_key, 'remoteip': remoteip, 'response': recaptcha_response_field, @@ -1439,6 +1311,8 @@ class AuthJWT(object): token = parts[1] else: token = current.request.vars.get(token_param) + if token is None: + raise HTTP(400, 'JWT header not found and JWT parameter {} missing in request'.format(token_param)) self.recvd_token = token return token @@ -2289,7 +2163,10 @@ class Auth(AuthAPI): user = None checks = [] # make a guess about who this user is - for fieldname in ['registration_id', 'username', 'email']: + guess_fields = ['registration_id', 'username', 'email'] + if self.settings.login_userfield: + guess_fields.append(self.settings.login_userfield) + for fieldname in guess_fields: if fieldname in table_user.fields() and \ keys.get(fieldname, None): checks.append(fieldname) @@ -2383,7 +2260,8 @@ class Auth(AuthAPI): def _get_login_settings(self): table_user = self.table_user() userfield = self.settings.login_userfield or 'username' \ - if 'username' in table_user.fields else 'email' + if self.settings.login_userfield or 'username' \ + in table_user.fields else 'email' passfield = self.settings.password_field return Storage({'table_user': table_user, 'userfield': userfield, @@ -2498,9 +2376,9 @@ class Auth(AuthAPI): success = True def build_response(body): - return '\n' +\ - TAG['cas:serviceResponse']( - body, **{'_xmlns:cas': 'http://www.yale.edu/tp/cas'}).xml() + xml_body = to_native(TAG['cas:serviceResponse']( + body, **{'_xmlns:cas': 'http://www.yale.edu/tp/cas'}).xml()) + return '\n' + xml_body if success: if version == 1: message = 'yes\n%s' % user[userfield] @@ -3051,7 +2929,7 @@ class Auth(AuthAPI): if self.settings.register_verify_password: if self.settings.register_fields is None: - self.settings.register_fields = [f.name for f in table_user if f.writable] + self.settings.register_fields = [f.name for f in table_user if f.writable and not f.compute] k = self.settings.register_fields.index(passfield) self.settings.register_fields.insert(k + 1, "password_two") extra_fields = [ @@ -3693,7 +3571,8 @@ class Auth(AuthAPI): requires = [requires] requires = list(filter(lambda t: isinstance(t, CRYPT), requires)) if requires: - requires[0].min_length = 0 + requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes + requires[0].min_length = 0 # But do not enforce minimum length for the old password form = SQLFORM.factory( Field('old_password', 'password', requires=requires, label=self.messages.old_password), @@ -3746,6 +3625,7 @@ class Auth(AuthAPI): client_side=self.settings.client_side) passfield = self.settings.password_field table_user[passfield].writable = False + table_user['email'].writable = False request = current.request session = current.session if next is DEFAULT: @@ -3756,6 +3636,7 @@ class Auth(AuthAPI): onaccept = self.settings.profile_onaccept if log is DEFAULT: log = self.messages['profile_log'] + form = SQLFORM( table_user, self.user.id, @@ -3777,9 +3658,9 @@ class Auth(AuthAPI): if any(f.compute for f in extra_fields): user = table_user[self.user.id] self._update_session_user(user) + self.update_groups() else: self.user.update(table_user._filter_fields(form.vars)) - session.flash = self.messages.profile_updated self.log_event(log, self.user) callback(onaccept, form) @@ -4737,7 +4618,7 @@ def fetch(url, data=None, headers=None, user_agent='Mozilla/5.0'): headers = headers or {} if data is not None: - data = urllib.urlencode(data) + data = urlencode(data) if user_agent: headers['User-agent'] = user_agent headers['Cookie'] = ' '.join( @@ -5052,7 +4933,7 @@ class Service(object): return f return _amfrpc3 - def soap(self, name=None, returns=None, args=None, doc=None): + def soap(self, name=None, returns=None, args=None, doc=None, response_element_name=None): """ Example: Use as:: @@ -5076,7 +4957,7 @@ class Service(object): """ def _soap(f): - self.soap_procedures[name or f.__name__] = f, returns, args, doc + self.soap_procedures[name or f.__name__] = f, returns, args, doc, response_element_name return f return _soap @@ -5382,8 +5263,8 @@ class Service(object): prefix='pys', documentation=documentation, ns=True) - for method, (function, returns, args, doc) in iteritems(procedures): - dispatcher.register_function(method, function, returns, args, doc) + for method, (function, returns, args, doc, resp_elem_name) in iteritems(procedures): + dispatcher.register_function(method, function, returns, args, doc, resp_elem_name) if request.env.request_method == 'POST': fault = {} # Process normal Soap Operation diff --git a/gluon/utf8.py b/gluon/utf8.py index 21fd12c4..34bfa87f 100644 --- a/gluon/utf8.py +++ b/gluon/utf8.py @@ -11,7 +11,7 @@ Utilities and class for UTF8 strings managing ---------------------------------------------- """ from __future__ import print_function -from gluon._compat import builtin as __builtin__, unicodeT, iteritems, to_unicode, to_native +from gluon._compat import builtin as __builtin__, unicodeT, iteritems, to_unicode, to_native, reload __all__ = ['Utf8'] diff --git a/gluon/utils.py b/gluon/utils.py index 3b26a51b..ea738047 100644 --- a/gluon/utils.py +++ b/gluon/utils.py @@ -207,6 +207,7 @@ def secure_dumps(data, encryption_key, hash_key=None, compression_level=None): def secure_loads(data, encryption_key, hash_key=None, compression_level=None): + data = to_bytes(data) components = data.count(b':') if components == 1: return secure_loads_deprecated(data, encryption_key, hash_key, compression_level) diff --git a/gluon/validators.py b/gluon/validators.py index 1d9dddcf..7e47f79c 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -21,7 +21,7 @@ import struct import decimal import unicodedata -from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, \ +from gluon._compat import StringIO, integer_types, basestring, unicodeT, 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 @@ -104,7 +104,7 @@ class Validator(object): Here is an example of how to require a validator for a table field:: - db.define_table('person', SQLField('name')) + db.define_table('person', Field('name')) db.person.name.requires=IS_NOT_EMPTY() Validators are always assigned using the requires attribute of a field. A @@ -633,7 +633,7 @@ class IS_IN_DB(Validator): if field.type in ('id', 'integer'): new_values = [] for value in values: - if not (isinstance(value, (int, long)) or value.isdigit()): + if not (isinstance(value, integer_types) or value.isdigit()): if self.auto_add: value = str(self.maybe_add(table, self.fieldnames[0], value)) else: @@ -660,7 +660,7 @@ class IS_IN_DB(Validator): return (values, None) else: if field.type in ('id', 'integer'): - if isinstance(value, (int, long)) or (isinstance(value, string_types) and value.isdigit()): + if isinstance(value, integer_types) or (isinstance(value, string_types) and value.isdigit()): value = int(value) elif self.auto_add: value = self.maybe_add(table, self.fieldnames[0], value) @@ -670,7 +670,7 @@ class IS_IN_DB(Validator): try: value = int(value) except TypeError: - return (values, translate(self.error_message)) + return (value, translate(self.error_message)) if self.theset: if str(value) in self.theset: @@ -757,7 +757,7 @@ def range_error_message(error_message, what_to_enter, minimum, maximum): error_message += ' greater than or equal to %(min)g' elif maximum is not None: error_message += ' less than or equal to %(max)g' - if type(maximum) in [int, long]: + if type(maximum) in integer_types: maximum -= 1 return translate(error_message) % dict(min=minimum, max=maximum) @@ -996,7 +996,7 @@ def is_empty(value, empty_regex=None): value = value.strip() if empty_regex is not None and empty_regex.match(value): value = '' - if value is None or value == '' or value == []: + if value is None or value == '' or value == b'' or value == []: return (_value, True) return (_value, False) @@ -1231,12 +1231,12 @@ class IS_LIST_OF_EMAILS(object): Example: Used as:: - Field('emails','list:string', - widget=SQLFORM.widgets.text.widget, - requires=IS_LIST_OF_EMAILS(), - represent=lambda v,r: \ - SPAN(*[A(x,_href='mailto:'+x) for x in (v or [])]) - ) + Field('emails', 'list:string', + widget=SQLFORM.widgets.text.widget, + requires=IS_LIST_OF_EMAILS(), + represent=lambda v, r: \ + XML(', '.join([A(x, _href='mailto:'+x).xml() for x in (v or [])])) + ) """ split_emails = re.compile('[^,;\s]+') @@ -1656,173 +1656,292 @@ class IS_GENERIC_URL(Validator): # else the URL is not valid return (value, translate(self.error_message)) -# Sources (obtained 2015-Feb-24): +# Sources (obtained 2017-Nov-11): # http://data.iana.org/TLD/tlds-alpha-by-domain.txt # see scripts/parse_top_level_domains.py for an easy update official_top_level_domains = [ # a - 'abogado', 'ac', 'academy', 'accountants', 'active', 'actor', - 'ad', 'adult', 'ae', 'aero', 'af', 'ag', 'agency', 'ai', - 'airforce', 'al', 'allfinanz', 'alsace', 'am', 'amsterdam', 'an', - 'android', 'ao', 'apartments', 'aq', 'aquarelle', 'ar', 'archi', - 'army', 'arpa', 'as', 'asia', 'associates', 'at', 'attorney', - 'au', 'auction', 'audio', 'autos', 'aw', 'ax', 'axa', 'az', + 'aaa', 'aarp', 'abarth', 'abb', 'abbott', 'abbvie', 'abc', + 'able', 'abogado', 'abudhabi', 'ac', 'academy', 'accenture', + 'accountant', 'accountants', 'aco', 'active', 'actor', 'ad', + 'adac', 'ads', 'adult', 'ae', 'aeg', 'aero', 'aetna', 'af', + 'afamilycompany', 'afl', 'africa', 'ag', 'agakhan', 'agency', + 'ai', 'aig', 'aigo', 'airbus', 'airforce', 'airtel', 'akdn', + 'al', 'alfaromeo', 'alibaba', 'alipay', 'allfinanz', 'allstate', + 'ally', 'alsace', 'alstom', 'am', 'americanexpress', + 'americanfamily', 'amex', 'amfam', 'amica', 'amsterdam', + 'analytics', 'android', 'anquan', 'anz', 'ao', 'aol', + 'apartments', 'app', 'apple', 'aq', 'aquarelle', 'ar', 'arab', + 'aramco', 'archi', 'army', 'arpa', 'art', 'arte', 'as', 'asda', + 'asia', 'associates', 'at', 'athleta', 'attorney', 'au', + 'auction', 'audi', 'audible', 'audio', 'auspost', 'author', + 'auto', 'autos', 'avianca', 'aw', 'aws', 'ax', 'axa', 'az', + 'azure', # b - 'ba', 'band', 'bank', 'bar', 'barclaycard', 'barclays', - 'bargains', 'bayern', 'bb', 'bd', 'be', 'beer', 'berlin', 'best', - 'bf', 'bg', 'bh', 'bi', 'bid', 'bike', 'bingo', 'bio', 'biz', - 'bj', 'black', 'blackfriday', 'bloomberg', 'blue', 'bm', 'bmw', - 'bn', 'bnpparibas', 'bo', 'boo', 'boutique', 'br', 'brussels', - 'bs', 'bt', 'budapest', 'build', 'builders', 'business', 'buzz', - 'bv', 'bw', 'by', 'bz', 'bzh', + 'ba', 'baby', 'baidu', 'banamex', 'bananarepublic', 'band', + 'bank', 'bar', 'barcelona', 'barclaycard', 'barclays', + 'barefoot', 'bargains', 'baseball', 'basketball', 'bauhaus', + 'bayern', 'bb', 'bbc', 'bbt', 'bbva', 'bcg', 'bcn', 'bd', 'be', + 'beats', 'beauty', 'beer', 'bentley', 'berlin', 'best', + 'bestbuy', 'bet', 'bf', 'bg', 'bh', 'bharti', 'bi', 'bible', + 'bid', 'bike', 'bing', 'bingo', 'bio', 'biz', 'bj', 'black', + 'blackfriday', 'blanco', 'blockbuster', 'blog', 'bloomberg', + 'blue', 'bm', 'bms', 'bmw', 'bn', 'bnl', 'bnpparibas', 'bo', + 'boats', 'boehringer', 'bofa', 'bom', 'bond', 'boo', 'book', + 'booking', 'boots', 'bosch', 'bostik', 'boston', 'bot', + 'boutique', 'box', 'br', 'bradesco', 'bridgestone', 'broadway', + 'broker', 'brother', 'brussels', 'bs', 'bt', 'budapest', + 'bugatti', 'build', 'builders', 'business', 'buy', 'buzz', 'bv', + 'bw', 'by', 'bz', 'bzh', # c - 'ca', 'cab', 'cal', 'camera', 'camp', 'cancerresearch', 'canon', - 'capetown', 'capital', 'caravan', 'cards', 'care', 'career', - 'careers', 'cartier', 'casa', 'cash', 'casino', 'cat', - 'catering', 'cbn', 'cc', 'cd', 'center', 'ceo', 'cern', 'cf', - 'cg', 'ch', 'channel', 'chat', 'cheap', 'christmas', 'chrome', - 'church', 'ci', 'citic', 'city', 'ck', 'cl', 'claims', - 'cleaning', 'click', 'clinic', 'clothing', 'club', 'cm', 'cn', - 'co', 'coach', 'codes', 'coffee', 'college', 'cologne', 'com', - 'community', 'company', 'computer', 'condos', 'construction', - 'consulting', 'contractors', 'cooking', 'cool', 'coop', - 'country', 'cr', 'credit', 'creditcard', 'cricket', 'crs', - 'cruises', 'cu', 'cuisinella', 'cv', 'cw', 'cx', 'cy', 'cymru', - 'cz', + 'ca', 'cab', 'cafe', 'cal', 'call', 'calvinklein', 'cam', + 'camera', 'camp', 'cancerresearch', 'canon', 'capetown', + 'capital', 'capitalone', 'car', 'caravan', 'cards', 'care', + 'career', 'careers', 'cars', 'cartier', 'casa', 'case', 'caseih', + 'cash', 'casino', 'cat', 'catering', 'catholic', 'cba', 'cbn', + 'cbre', 'cbs', 'cc', 'cd', 'ceb', 'center', 'ceo', 'cern', 'cf', + 'cfa', 'cfd', 'cg', 'ch', 'chanel', 'channel', 'chase', 'chat', + 'cheap', 'chintai', 'christmas', 'chrome', 'chrysler', 'church', + 'ci', 'cipriani', 'circle', 'cisco', 'citadel', 'citi', 'citic', + 'city', 'cityeats', 'ck', 'cl', 'claims', 'cleaning', 'click', + 'clinic', 'clinique', 'clothing', 'cloud', 'club', 'clubmed', + 'cm', 'cn', 'co', 'coach', 'codes', 'coffee', 'college', + 'cologne', 'com', 'comcast', 'commbank', 'community', 'company', + 'compare', 'computer', 'comsec', 'condos', 'construction', + 'consulting', 'contact', 'contractors', 'cooking', + 'cookingchannel', 'cool', 'coop', 'corsica', 'country', 'coupon', + 'coupons', 'courses', 'cr', 'credit', 'creditcard', + 'creditunion', 'cricket', 'crown', 'crs', 'cruise', 'cruises', + 'csc', 'cu', 'cuisinella', 'cv', 'cw', 'cx', 'cy', 'cymru', + 'cyou', 'cz', # d - 'dabur', 'dad', 'dance', 'dating', 'day', 'dclk', 'de', 'deals', - 'degree', 'delivery', 'democrat', 'dental', 'dentist', 'desi', - 'design', 'dev', 'diamonds', 'diet', 'digital', 'direct', - 'directory', 'discount', 'dj', 'dk', 'dm', 'dnp', 'do', 'docs', - 'domains', 'doosan', 'durban', 'dvag', 'dz', + 'dabur', 'dad', 'dance', 'data', 'date', 'dating', 'datsun', + 'day', 'dclk', 'dds', 'de', 'deal', 'dealer', 'deals', 'degree', + 'delivery', 'dell', 'deloitte', 'delta', 'democrat', 'dental', + 'dentist', 'desi', 'design', 'dev', 'dhl', 'diamonds', 'diet', + 'digital', 'direct', 'directory', 'discount', 'discover', 'dish', + 'diy', 'dj', 'dk', 'dm', 'dnp', 'do', 'docs', 'doctor', 'dodge', + 'dog', 'doha', 'domains', 'dot', 'download', 'drive', 'dtv', + 'dubai', 'duck', 'dunlop', 'duns', 'dupont', 'durban', 'dvag', + 'dvr', 'dz', # e - 'eat', 'ec', 'edu', 'education', 'ee', 'eg', 'email', 'emerck', - 'energy', 'engineer', 'engineering', 'enterprises', 'equipment', - 'er', 'es', 'esq', 'estate', 'et', 'eu', 'eurovision', 'eus', - 'events', 'everbank', 'exchange', 'expert', 'exposed', + 'earth', 'eat', 'ec', 'eco', 'edeka', 'edu', 'education', 'ee', + 'eg', 'email', 'emerck', 'energy', 'engineer', 'engineering', + 'enterprises', 'epost', 'epson', 'equipment', 'er', 'ericsson', + 'erni', 'es', 'esq', 'estate', 'esurance', 'et', 'etisalat', + 'eu', 'eurovision', 'eus', 'events', 'everbank', 'exchange', + 'expert', 'exposed', 'express', 'extraspace', # f - 'fail', 'fans', 'farm', 'fashion', 'feedback', 'fi', 'finance', - 'financial', 'firmdale', 'fish', 'fishing', 'fit', 'fitness', - 'fj', 'fk', 'flights', 'florist', 'flowers', 'flsmidth', 'fly', - 'fm', 'fo', 'foo', 'football', 'forsale', 'foundation', 'fr', - 'frl', 'frogans', 'fund', 'furniture', 'futbol', + 'fage', 'fail', 'fairwinds', 'faith', 'family', 'fan', 'fans', + 'farm', 'farmers', 'fashion', 'fast', 'fedex', 'feedback', + 'ferrari', 'ferrero', 'fi', 'fiat', 'fidelity', 'fido', 'film', + 'final', 'finance', 'financial', 'fire', 'firestone', 'firmdale', + 'fish', 'fishing', 'fit', 'fitness', 'fj', 'fk', 'flickr', + 'flights', 'flir', 'florist', 'flowers', 'fly', 'fm', 'fo', + 'foo', 'food', 'foodnetwork', 'football', 'ford', 'forex', + 'forsale', 'forum', 'foundation', 'fox', 'fr', 'free', + 'fresenius', 'frl', 'frogans', 'frontdoor', 'frontier', 'ftr', + 'fujitsu', 'fujixerox', 'fun', 'fund', 'furniture', 'futbol', + 'fyi', # g - 'ga', 'gal', 'gallery', 'garden', 'gb', 'gbiz', 'gd', 'gdn', - 'ge', 'gent', 'gf', 'gg', 'ggee', 'gh', 'gi', 'gift', 'gifts', - 'gives', 'gl', 'glass', 'gle', 'global', 'globo', 'gm', 'gmail', - 'gmo', 'gmx', 'gn', 'goldpoint', 'goog', 'google', 'gop', 'gov', - 'gp', 'gq', 'gr', 'graphics', 'gratis', 'green', 'gripe', 'gs', - 'gt', 'gu', 'guide', 'guitars', 'guru', 'gw', 'gy', + 'ga', 'gal', 'gallery', 'gallo', 'gallup', 'game', 'games', + 'gap', 'garden', 'gb', 'gbiz', 'gd', 'gdn', 'ge', 'gea', 'gent', + 'genting', 'george', 'gf', 'gg', 'ggee', 'gh', 'gi', 'gift', + 'gifts', 'gives', 'giving', 'gl', 'glade', 'glass', 'gle', + 'global', 'globo', 'gm', 'gmail', 'gmbh', 'gmo', 'gmx', 'gn', + 'godaddy', 'gold', 'goldpoint', 'golf', 'goo', 'goodhands', + 'goodyear', 'goog', 'google', 'gop', 'got', 'gov', 'gp', 'gq', + 'gr', 'grainger', 'graphics', 'gratis', 'green', 'gripe', + 'grocery', 'group', 'gs', 'gt', 'gu', 'guardian', 'gucci', + 'guge', 'guide', 'guitars', 'guru', 'gw', 'gy', # h - 'hamburg', 'hangout', 'haus', 'healthcare', 'help', 'here', - 'hermes', 'hiphop', 'hiv', 'hk', 'hm', 'hn', 'holdings', - 'holiday', 'homes', 'horse', 'host', 'hosting', 'house', 'how', - 'hr', 'ht', 'hu', + 'hair', 'hamburg', 'hangout', 'haus', 'hbo', 'hdfc', 'hdfcbank', + 'health', 'healthcare', 'help', 'helsinki', 'here', 'hermes', + 'hgtv', 'hiphop', 'hisamitsu', 'hitachi', 'hiv', 'hk', 'hkt', + 'hm', 'hn', 'hockey', 'holdings', 'holiday', 'homedepot', + 'homegoods', 'homes', 'homesense', 'honda', 'honeywell', 'horse', + 'hospital', 'host', 'hosting', 'hot', 'hoteles', 'hotels', + 'hotmail', 'house', 'how', 'hr', 'hsbc', 'ht', 'hu', 'hughes', + 'hyatt', 'hyundai', # i - 'ibm', 'id', 'ie', 'ifm', 'il', 'im', 'immo', 'immobilien', 'in', - 'industries', 'info', 'ing', 'ink', 'institute', 'insure', 'int', - 'international', 'investments', 'io', 'iq', 'ir', 'irish', 'is', - 'it', 'iwc', + 'ibm', 'icbc', 'ice', 'icu', 'id', 'ie', 'ieee', 'ifm', 'ikano', + 'il', 'im', 'imamat', 'imdb', 'immo', 'immobilien', 'in', + 'industries', 'infiniti', 'info', 'ing', 'ink', 'institute', + 'insurance', 'insure', 'int', 'intel', 'international', 'intuit', + 'investments', 'io', 'ipiranga', 'iq', 'ir', 'irish', 'is', + 'iselect', 'ismaili', 'ist', 'istanbul', 'it', 'itau', 'itv', + 'iveco', 'iwc', # j - 'jcb', 'je', 'jetzt', 'jm', 'jo', 'jobs', 'joburg', 'jp', - 'juegos', + 'jaguar', 'java', 'jcb', 'jcp', 'je', 'jeep', 'jetzt', 'jewelry', + 'jio', 'jlc', 'jll', 'jm', 'jmp', 'jnj', 'jo', 'jobs', 'joburg', + 'jot', 'joy', 'jp', 'jpmorgan', 'jprs', 'juegos', 'juniper', # k - 'kaufen', 'kddi', 'ke', 'kg', 'kh', 'ki', 'kim', 'kitchen', - 'kiwi', 'km', 'kn', 'koeln', 'kp', 'kr', 'krd', 'kred', 'kw', - 'ky', 'kyoto', 'kz', + 'kaufen', 'kddi', 'ke', 'kerryhotels', 'kerrylogistics', + 'kerryproperties', 'kfh', 'kg', 'kh', 'ki', 'kia', 'kim', + 'kinder', 'kindle', 'kitchen', 'kiwi', 'km', 'kn', 'koeln', + 'komatsu', 'kosher', 'kp', 'kpmg', 'kpn', 'kr', 'krd', 'kred', + 'kuokgroup', 'kw', 'ky', 'kyoto', 'kz', # l - 'la', 'lacaixa', 'land', 'lat', 'latrobe', 'lawyer', 'lb', 'lc', - 'lds', 'lease', 'legal', 'lgbt', 'li', 'lidl', 'life', - 'lighting', 'limited', 'limo', 'link', 'lk', 'loans', - 'localhost', 'london', 'lotte', 'lotto', 'lr', 'ls', 'lt', - 'ltda', 'lu', 'luxe', 'luxury', 'lv', 'ly', + 'la', 'lacaixa', 'ladbrokes', 'lamborghini', 'lamer', + 'lancaster', 'lancia', 'lancome', 'land', 'landrover', 'lanxess', + 'lasalle', 'lat', 'latino', 'latrobe', 'law', 'lawyer', 'lb', + 'lc', 'lds', 'lease', 'leclerc', 'lefrak', 'legal', 'lego', + 'lexus', 'lgbt', 'li', 'liaison', 'lidl', 'life', + 'lifeinsurance', 'lifestyle', 'lighting', 'like', 'lilly', + 'limited', 'limo', 'lincoln', 'linde', 'link', 'lipsy', 'live', + 'living', 'lixil', 'lk', 'loan', 'loans', 'localhost', 'locker', + 'locus', 'loft', 'lol', 'london', 'lotte', 'lotto', 'love', + 'lpl', 'lplfinancial', 'lr', 'ls', 'lt', 'ltd', 'ltda', 'lu', + 'lundbeck', 'lupin', 'luxe', 'luxury', 'lv', 'ly', # m - 'ma', 'madrid', 'maison', 'management', 'mango', 'market', - 'marketing', 'marriott', 'mc', 'md', 'me', 'media', 'meet', - 'melbourne', 'meme', 'memorial', 'menu', 'mg', 'mh', 'miami', - 'mil', 'mini', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'moda', - 'moe', 'monash', 'money', 'mormon', 'mortgage', 'moscow', - 'motorcycles', 'mov', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', - 'museum', 'mv', 'mw', 'mx', 'my', 'mz', + 'ma', 'macys', 'madrid', 'maif', 'maison', 'makeup', 'man', + 'management', 'mango', 'map', 'market', 'marketing', 'markets', + 'marriott', 'marshalls', 'maserati', 'mattel', 'mba', 'mc', + 'mckinsey', 'md', 'me', 'med', 'media', 'meet', 'melbourne', + 'meme', 'memorial', 'men', 'menu', 'meo', 'merckmsd', 'metlife', + 'mg', 'mh', 'miami', 'microsoft', 'mil', 'mini', 'mint', 'mit', + 'mitsubishi', 'mk', 'ml', 'mlb', 'mls', 'mm', 'mma', 'mn', 'mo', + 'mobi', 'mobile', 'mobily', 'moda', 'moe', 'moi', 'mom', + 'monash', 'money', 'monster', 'mopar', 'mormon', 'mortgage', + 'moscow', 'moto', 'motorcycles', 'mov', 'movie', 'movistar', + 'mp', 'mq', 'mr', 'ms', 'msd', 'mt', 'mtn', 'mtr', 'mu', + 'museum', 'mutual', 'mv', 'mw', 'mx', 'my', 'mz', # n - 'na', 'nagoya', 'name', 'navy', 'nc', 'ne', 'net', 'network', - 'neustar', 'new', 'nexus', 'nf', 'ng', 'ngo', 'nhk', 'ni', - 'nico', 'ninja', 'nl', 'no', 'np', 'nr', 'nra', 'nrw', 'ntt', - 'nu', 'nyc', 'nz', + 'na', 'nab', 'nadex', 'nagoya', 'name', 'nationwide', 'natura', + 'navy', 'nba', 'nc', 'ne', 'nec', 'net', 'netbank', 'netflix', + 'network', 'neustar', 'new', 'newholland', 'news', 'next', + 'nextdirect', 'nexus', 'nf', 'nfl', 'ng', 'ngo', 'nhk', 'ni', + 'nico', 'nike', 'nikon', 'ninja', 'nissan', 'nissay', 'nl', 'no', + 'nokia', 'northwesternmutual', 'norton', 'now', 'nowruz', + 'nowtv', 'np', 'nr', 'nra', 'nrw', 'ntt', 'nu', 'nyc', 'nz', # o - 'okinawa', 'om', 'one', 'ong', 'onl', 'ooo', 'org', 'organic', - 'osaka', 'otsuka', 'ovh', + 'obi', 'observer', 'off', 'office', 'okinawa', 'olayan', + 'olayangroup', 'oldnavy', 'ollo', 'om', 'omega', 'one', 'ong', + 'onl', 'online', 'onyourside', 'ooo', 'open', 'oracle', 'orange', + 'org', 'organic', 'origins', 'osaka', 'otsuka', 'ott', 'ovh', # p - 'pa', 'paris', 'partners', 'parts', 'party', 'pe', 'pf', 'pg', - 'ph', 'pharmacy', 'photo', 'photography', 'photos', 'physio', - 'pics', 'pictures', 'pink', 'pizza', 'pk', 'pl', 'place', - 'plumbing', 'pm', 'pn', 'pohl', 'poker', 'porn', 'post', 'pr', - 'praxi', 'press', 'pro', 'prod', 'productions', 'prof', - 'properties', 'property', 'ps', 'pt', 'pub', 'pw', 'py', + 'pa', 'page', 'panasonic', 'panerai', 'paris', 'pars', + 'partners', 'parts', 'party', 'passagens', 'pay', 'pccw', 'pe', + 'pet', 'pf', 'pfizer', 'pg', 'ph', 'pharmacy', 'phd', 'philips', + 'phone', 'photo', 'photography', 'photos', 'physio', 'piaget', + 'pics', 'pictet', 'pictures', 'pid', 'pin', 'ping', 'pink', + 'pioneer', 'pizza', 'pk', 'pl', 'place', 'play', 'playstation', + 'plumbing', 'plus', 'pm', 'pn', 'pnc', 'pohl', 'poker', + 'politie', 'porn', 'post', 'pr', 'pramerica', 'praxi', 'press', + 'prime', 'pro', 'prod', 'productions', 'prof', 'progressive', + 'promo', 'properties', 'property', 'protection', 'pru', + 'prudential', 'ps', 'pt', 'pub', 'pw', 'pwc', 'py', # q - 'qa', 'qpon', 'quebec', + 'qa', 'qpon', 'quebec', 'quest', 'qvc', # r - 're', 'realtor', 'recipes', 'red', 'rehab', 'reise', 'reisen', - 'reit', 'ren', 'rentals', 'repair', 'report', 'republican', - 'rest', 'restaurant', 'reviews', 'rich', 'rio', 'rip', 'ro', - 'rocks', 'rodeo', 'rs', 'rsvp', 'ru', 'ruhr', 'rw', 'ryukyu', + 'racing', 'radio', 'raid', 're', 'read', 'realestate', 'realtor', + 'realty', 'recipes', 'red', 'redstone', 'redumbrella', 'rehab', + 'reise', 'reisen', 'reit', 'reliance', 'ren', 'rent', 'rentals', + 'repair', 'report', 'republican', 'rest', 'restaurant', 'review', + 'reviews', 'rexroth', 'rich', 'richardli', 'ricoh', + 'rightathome', 'ril', 'rio', 'rip', 'rmit', 'ro', 'rocher', + 'rocks', 'rodeo', 'rogers', 'room', 'rs', 'rsvp', 'ru', 'rugby', + 'ruhr', 'run', 'rw', 'rwe', 'ryukyu', # s - 'sa', 'saarland', 'sale', 'samsung', 'sarl', 'saxo', 'sb', 'sc', - 'sca', 'scb', 'schmidt', 'school', 'schule', 'schwarz', - 'science', 'scot', 'sd', 'se', 'services', 'sew', 'sexy', 'sg', - 'sh', 'shiksha', 'shoes', 'shriram', 'si', 'singles', 'sj', 'sk', - 'sky', 'sl', 'sm', 'sn', 'so', 'social', 'software', 'sohu', - 'solar', 'solutions', 'soy', 'space', 'spiegel', 'sr', 'st', - 'style', 'su', 'supplies', 'supply', 'support', 'surf', - 'surgery', 'suzuki', 'sv', 'sx', 'sy', 'sydney', 'systems', 'sz', + 'sa', 'saarland', 'safe', 'safety', 'sakura', 'sale', 'salon', + 'samsclub', 'samsung', 'sandvik', 'sandvikcoromant', 'sanofi', + 'sap', 'sapo', 'sarl', 'sas', 'save', 'saxo', 'sb', 'sbi', 'sbs', + 'sc', 'sca', 'scb', 'schaeffler', 'schmidt', 'scholarships', + 'school', 'schule', 'schwarz', 'science', 'scjohnson', 'scor', + 'scot', 'sd', 'se', 'search', 'seat', 'secure', 'security', + 'seek', 'select', 'sener', 'services', 'ses', 'seven', 'sew', + 'sex', 'sexy', 'sfr', 'sg', 'sh', 'shangrila', 'sharp', 'shaw', + 'shell', 'shia', 'shiksha', 'shoes', 'shop', 'shopping', + 'shouji', 'show', 'showtime', 'shriram', 'si', 'silk', 'sina', + 'singles', 'site', 'sj', 'sk', 'ski', 'skin', 'sky', 'skype', + 'sl', 'sling', 'sm', 'smart', 'smile', 'sn', 'sncf', 'so', + 'soccer', 'social', 'softbank', 'software', 'sohu', 'solar', + 'solutions', 'song', 'sony', 'soy', 'space', 'spiegel', 'spot', + 'spreadbetting', 'sr', 'srl', 'srt', 'st', 'stada', 'staples', + 'star', 'starhub', 'statebank', 'statefarm', 'statoil', 'stc', + 'stcgroup', 'stockholm', 'storage', 'store', 'stream', 'studio', + 'study', 'style', 'su', 'sucks', 'supplies', 'supply', 'support', + 'surf', 'surgery', 'suzuki', 'sv', 'swatch', 'swiftcover', + 'swiss', 'sx', 'sy', 'sydney', 'symantec', 'systems', 'sz', # t - 'taipei', 'tatar', 'tattoo', 'tax', 'tc', 'td', 'technology', - 'tel', 'temasek', 'tennis', 'tf', 'tg', 'th', 'tienda', 'tips', - 'tires', 'tirol', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'today', - 'tokyo', 'tools', 'top', 'toshiba', 'town', 'toys', 'tp', 'tr', - 'trade', 'training', 'travel', 'trust', 'tt', 'tui', 'tv', 'tw', - 'tz', + 'tab', 'taipei', 'talk', 'taobao', 'target', 'tatamotors', + 'tatar', 'tattoo', 'tax', 'taxi', 'tc', 'tci', 'td', 'tdk', + 'team', 'tech', 'technology', 'tel', 'telecity', 'telefonica', + 'temasek', 'tennis', 'teva', 'tf', 'tg', 'th', 'thd', 'theater', + 'theatre', 'tiaa', 'tickets', 'tienda', 'tiffany', 'tips', + 'tires', 'tirol', 'tj', 'tjmaxx', 'tjx', 'tk', 'tkmaxx', 'tl', + 'tm', 'tmall', 'tn', 'to', 'today', 'tokyo', 'tools', 'top', + 'toray', 'toshiba', 'total', 'tours', 'town', 'toyota', 'toys', + 'tr', 'trade', 'trading', 'training', 'travel', 'travelchannel', + 'travelers', 'travelersinsurance', 'trust', 'trv', 'tt', 'tube', + 'tui', 'tunes', 'tushu', 'tv', 'tvs', 'tw', 'tz', # u - 'ua', 'ug', 'uk', 'university', 'uno', 'uol', 'us', 'uy', 'uz', + 'ua', 'ubank', 'ubs', 'uconnect', 'ug', 'uk', 'unicom', + 'university', 'uno', 'uol', 'ups', 'us', 'uy', 'uz', # v - 'va', 'vacations', 'vc', 've', 'vegas', 'ventures', - 'versicherung', 'vet', 'vg', 'vi', 'viajes', 'video', 'villas', - 'vision', 'vlaanderen', 'vn', 'vodka', 'vote', 'voting', 'voto', - 'voyage', 'vu', + 'va', 'vacations', 'vana', 'vanguard', 'vc', 've', 'vegas', + 'ventures', 'verisign', 'versicherung', 'vet', 'vg', 'vi', + 'viajes', 'video', 'vig', 'viking', 'villas', 'vin', 'vip', + 'virgin', 'visa', 'vision', 'vista', 'vistaprint', 'viva', + 'vivo', 'vlaanderen', 'vn', 'vodka', 'volkswagen', 'volvo', + 'vote', 'voting', 'voto', 'voyage', 'vu', 'vuelos', # w - 'wales', 'wang', 'watch', 'webcam', 'website', 'wed', 'wedding', - 'wf', 'whoswho', 'wien', 'wiki', 'williamhill', 'wme', 'work', - 'works', 'world', 'ws', 'wtc', 'wtf', + 'wales', 'walmart', 'walter', 'wang', 'wanggou', 'warman', + 'watch', 'watches', 'weather', 'weatherchannel', 'webcam', + 'weber', 'website', 'wed', 'wedding', 'weibo', 'weir', 'wf', + 'whoswho', 'wien', 'wiki', 'williamhill', 'win', 'windows', + 'wine', 'winners', 'wme', 'wolterskluwer', 'woodside', 'work', + 'works', 'world', 'wow', 'ws', 'wtc', 'wtf', # x - 'xn--1qqw23a', 'xn--3bst00m', 'xn--3ds443g', 'xn--3e0b707e', - 'xn--45brj9c', 'xn--45q11c', 'xn--4gbrim', 'xn--55qw42g', - 'xn--55qx5d', 'xn--6frz82g', 'xn--6qq986b3xl', 'xn--80adxhks', - 'xn--80ao21a', 'xn--80asehdb', 'xn--80aswg', 'xn--90a3ac', - 'xn--90ais', 'xn--b4w605ferd', 'xn--c1avg', 'xn--cg4bki', - 'xn--clchc0ea0b2g2a9gcd', 'xn--czr694b', 'xn--czrs0t', - 'xn--czru2d', 'xn--d1acj3b', 'xn--d1alf', 'xn--fiq228c5hs', - 'xn--fiq64b', 'xn--fiqs8s', 'xn--fiqz9s', 'xn--flw351e', - 'xn--fpcrj9c3d', 'xn--fzc2c9e2c', 'xn--gecrj9c', 'xn--h2brj9c', - 'xn--hxt814e', 'xn--i1b6b1a6a2e', 'xn--io0a7i', 'xn--j1amh', - 'xn--j6w193g', 'xn--kprw13d', 'xn--kpry57d', 'xn--kput3i', - 'xn--l1acc', 'xn--lgbbat1ad8j', 'xn--mgb9awbf', - 'xn--mgba3a4f16a', 'xn--mgbaam7a8h', 'xn--mgbab2bd', - 'xn--mgbayh7gpa', 'xn--mgbbh1a71e', 'xn--mgbc0a9azcg', - 'xn--mgberp4a5d4ar', 'xn--mgbx4cd0ab', 'xn--ngbc5azd', - 'xn--node', 'xn--nqv7f', 'xn--nqv7fs00ema', 'xn--o3cw4h', - 'xn--ogbpf8fl', 'xn--p1acf', 'xn--p1ai', 'xn--pgbs0dh', - 'xn--q9jyb4c', 'xn--qcka1pmc', 'xn--rhqv96g', 'xn--s9brj9c', - 'xn--ses554g', 'xn--unup4y', 'xn--vermgensberater-ctb', - 'xn--vermgensberatung-pwb', 'xn--vhquv', 'xn--wgbh1c', + 'xbox', 'xerox', 'xfinity', 'xihuan', 'xin', 'xn--11b4c3d', + 'xn--1ck2e1b', 'xn--1qqw23a', 'xn--2scrj9c', 'xn--30rr7y', + 'xn--3bst00m', 'xn--3ds443g', 'xn--3e0b707e', 'xn--3hcrj9c', + 'xn--3oq18vl8pn36a', 'xn--3pxu8k', 'xn--42c2d9a', 'xn--45br5cyl', + 'xn--45brj9c', 'xn--45q11c', 'xn--4gbrim', 'xn--54b7fta0cc', + 'xn--55qw42g', 'xn--55qx5d', 'xn--5su34j936bgsg', 'xn--5tzm5g', + 'xn--6frz82g', 'xn--6qq986b3xl', 'xn--80adxhks', 'xn--80ao21a', + 'xn--80aqecdr1a', 'xn--80asehdb', 'xn--80aswg', 'xn--8y0a063a', + 'xn--90a3ac', 'xn--90ae', 'xn--90ais', 'xn--9dbq2a', + 'xn--9et52u', 'xn--9krt00a', 'xn--b4w605ferd', + 'xn--bck1b9a5dre4c', 'xn--c1avg', 'xn--c2br7g', 'xn--cck2b3b', + 'xn--cg4bki', 'xn--clchc0ea0b2g2a9gcd', 'xn--czr694b', + 'xn--czrs0t', 'xn--czru2d', 'xn--d1acj3b', 'xn--d1alf', + 'xn--e1a4c', 'xn--eckvdtc9d', 'xn--efvy88h', 'xn--estv75g', + 'xn--fct429k', 'xn--fhbei', 'xn--fiq228c5hs', 'xn--fiq64b', + 'xn--fiqs8s', 'xn--fiqz9s', 'xn--fjq720a', 'xn--flw351e', + 'xn--fpcrj9c3d', 'xn--fzc2c9e2c', 'xn--fzys8d69uvgm', + 'xn--g2xx48c', 'xn--gckr3f0f', 'xn--gecrj9c', 'xn--gk3at1e', + 'xn--h2breg3eve', 'xn--h2brj9c', 'xn--h2brj9c8c', 'xn--hxt814e', + 'xn--i1b6b1a6a2e', 'xn--imr513n', 'xn--io0a7i', 'xn--j1aef', + 'xn--j1amh', 'xn--j6w193g', 'xn--jlq61u9w7b', 'xn--jvr189m', + 'xn--kcrx77d1x4a', 'xn--kprw13d', 'xn--kpry57d', 'xn--kpu716f', + 'xn--kput3i', 'xn--l1acc', 'xn--lgbbat1ad8j', 'xn--mgb9awbf', + 'xn--mgba3a3ejt', 'xn--mgba3a4f16a', 'xn--mgba7c0bbn0a', + 'xn--mgbaakc7dvf', 'xn--mgbaam7a8h', 'xn--mgbab2bd', + 'xn--mgbai9azgqp6j', 'xn--mgbayh7gpa', 'xn--mgbb9fbpob', + 'xn--mgbbh1a', 'xn--mgbbh1a71e', 'xn--mgbc0a9azcg', + 'xn--mgbca7dzdo', 'xn--mgberp4a5d4ar', 'xn--mgbgu82a', + 'xn--mgbi4ecexp', 'xn--mgbpl2fh', 'xn--mgbt3dhd', 'xn--mgbtx2b', + 'xn--mgbx4cd0ab', 'xn--mix891f', 'xn--mk1bu44c', 'xn--mxtq1m', + 'xn--ngbc5azd', 'xn--ngbe9e0a', 'xn--ngbrx', 'xn--node', + 'xn--nqv7f', 'xn--nqv7fs00ema', 'xn--nyqy26a', 'xn--o3cw4h', + 'xn--ogbpf8fl', 'xn--p1acf', 'xn--p1ai', 'xn--pbt977c', + 'xn--pgbs0dh', 'xn--pssy2u', 'xn--q9jyb4c', 'xn--qcka1pmc', + 'xn--qxam', 'xn--rhqv96g', 'xn--rovu88b', 'xn--rvc1e0am3e', + 'xn--s9brj9c', 'xn--ses554g', 'xn--t60b56a', 'xn--tckwe', + 'xn--tiq49xqyj', 'xn--unup4y', 'xn--vermgensberater-ctb', + 'xn--vermgensberatung-pwb', 'xn--vhquv', 'xn--vuq861b', + 'xn--w4r85el8fhu5dnra', 'xn--w4rs40l', 'xn--wgbh1c', 'xn--wgbl6a', 'xn--xhq521b', 'xn--xkc2al3hye2a', - 'xn--xkc2dl3a5ee0h', 'xn--yfro4i67o', 'xn--ygbi2ammx', - 'xn--zfr164b', 'xxx', 'xyz', + 'xn--xkc2dl3a5ee0h', 'xn--y9a3aq', 'xn--yfro4i67o', + 'xn--ygbi2ammx', 'xn--zfr164b', 'xperia', 'xxx', 'xyz', # y - 'yachts', 'yandex', 'ye', 'yodobashi', 'yoga', 'yokohama', - 'youtube', 'yt', + 'yachts', 'yahoo', 'yamaxun', 'yandex', 'ye', 'yodobashi', + 'yoga', 'yokohama', 'you', 'youtube', 'yt', 'yun', # z - 'za', 'zip', 'zm', 'zone', 'zuerich', 'zw' + 'za', 'zappos', 'zara', 'zero', 'zip', 'zippo', 'zm', 'zone', + 'zuerich', 'zw' ] @@ -2449,12 +2568,11 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME): class IS_LIST_OF(Validator): - def __init__(self, other=None, minimum=0, maximum=100, - error_message=None): + def __init__(self, other=None, minimum=None, maximum=None, error_message=None): self.other = other self.minimum = minimum self.maximum = maximum - self.error_message = error_message or "Enter between %(min)g and %(max)g values" + self.error_message = error_message def __call__(self, value): ivalue = value @@ -2462,9 +2580,11 @@ class IS_LIST_OF(Validator): ivalue = [ivalue] ivalue = [i for i in ivalue if str(i).strip()] if self.minimum is not None and len(ivalue) < self.minimum: - return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum)) + return (ivalue, translate(self.error_message or + 'Minimum length is %(min)s') % dict(min=self.minimum, max=self.maximum)) if self.maximum is not None and len(ivalue) > self.maximum: - return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum)) + return (ivalue, translate(self.error_message or + 'Maximum length is %(max)s') % dict(min=self.minimum, max=self.maximum)) new_value = [] other = self.other if self.other: @@ -2621,14 +2741,17 @@ class ANY_OF(Validator): """ - def __init__(self, subs): + def __init__(self, subs, error_message=None): self.subs = subs + self.error_message = error_message def __call__(self, value): for validator in self.subs: value, error = validator(value) if error is None: break + if error is not None and self.error_message is not None: + error = translate(self.error_message) return value, error def formatter(self, value): @@ -3426,14 +3549,14 @@ class IS_IPV4(Validator): for bottom, top in zip(self.minip, self.maxip): if self.invert != (bottom <= number <= top): ok = True - if not (self.is_localhost is None or self.is_localhost == - (number == self.localhost)): + if ok and self.is_localhost is not None and \ + self.is_localhost != (number == self.localhost): ok = False - if not (self.is_private is None or self.is_private == - (sum([private_number[0] <= number <= private_number[1] - for private_number in self.private]) > 0)): + if ok and self.is_private is not None and (self.is_private != + any([private_number[0] <= number <= private_number[1] + for private_number in self.private])): ok = False - if not (self.is_automatic is None or self.is_automatic == + if ok and self.is_automatic is not None and (self.is_automatic != (self.automatic[0] <= number <= self.automatic[1])): ok = False if ok: @@ -3443,9 +3566,10 @@ class IS_IPV4(Validator): class IS_IPV6(Validator): """ - Checks if field's value is an IP version 6 address. First attempts to - use the ipaddress library and falls back to contrib/ipaddr.py from Google - (https://code.google.com/p/ipaddr-py/) + Checks if field's value is an IP version 6 address. + + Uses the ipaddress from the Python 3 standard library + and its Python 2 backport (in contrib/ipaddress.py). Args: is_private: None (default): indifferent @@ -3453,7 +3577,7 @@ class IS_IPV6(Validator): False (forbid): address must NOT be in fc00::/7 range is_link_local: Same as above but uses fe80::/10 range is_reserved: Same as above but uses IETF reserved range - is_mulicast: Same as above but uses ff00::/8 range + is_multicast: Same as above but uses ff00::/8 range is_routeable: Similar to above but enforces not private, link_local, reserved or multicast is_6to4: Same as above but uses 2002::/16 range @@ -3553,27 +3677,26 @@ class IS_IPV6(Validator): if self.is_routeable: self.is_private = False - self.is_link_local = False self.is_reserved = False self.is_multicast = False - if not (self.is_private is None or self.is_private == - ip.is_private): + if ok and self.is_private is not None and \ + self.is_private != ip.is_private: ok = False - if not (self.is_link_local is None or self.is_link_local == - ip.is_link_local): + if ok and self.is_link_local is not None and \ + self.is_link_local != ip.is_link_local: ok = False - if not (self.is_reserved is None or self.is_reserved == - ip.is_reserved): + if ok and self.is_reserved is not None and \ + self.is_reserved != ip.is_reserved: ok = False - if not (self.is_multicast is None or self.is_multicast == - ip.is_multicast): + if ok and self.is_multicast is not None and \ + self.is_multicast != ip.is_multicast: ok = False - if not (self.is_6to4 is None or self.is_6to4 == - ip.is_6to4): + if ok and self.is_6to4 is not None and \ + self.is_6to4 != bool(ip.sixtofour): ok = False - if not (self.is_teredo is None or self.is_teredo == - ip.is_teredo): + if ok and self.is_teredo is not None and \ + self.is_teredo != bool(ip.teredo): ok = False if ok: @@ -3588,8 +3711,8 @@ class IS_IPADDRESS(Validator): addresses from within a specific range. Checks are done with the correct IS_IPV4 and IS_IPV6 validators. - Uses ipaddress library if found, falls back to PEP-3144 ipaddr.py from - Google (in contrib). + Uses the ipaddress from the Python 3 standard library + and its Python 2 backport (in contrib/ipaddress.py). Args: minip: lowest allowed address; accepts: @@ -3622,7 +3745,7 @@ class IS_IPADDRESS(Validator): - is_link_local: Same as above but uses fe80::/10 range - is_reserved: Same as above but uses IETF reserved range - - is_mulicast: Same as above but uses ff00::/8 range + - is_multicast: Same as above but uses ff00::/8 range - is_routeable: Similar to above but enforces not private, link_local, reserved or multicast - is_6to4: Same as above but uses 2002::/16 range @@ -3741,7 +3864,7 @@ class IS_IPADDRESS(Validator): self.is_localhost = is_localhost self.is_private = is_private self.is_automatic = is_automatic - self.is_ipv4 = is_ipv4 + self.is_ipv4 = is_ipv4 or is_ipv6 is False self.is_private = is_private self.is_link_local = is_link_local self.is_reserved = is_reserved @@ -3750,7 +3873,7 @@ class IS_IPADDRESS(Validator): self.is_6to4 = is_6to4 self.is_teredo = is_teredo self.subnets = subnets - self.is_ipv6 = is_ipv6 + self.is_ipv6 = is_ipv6 or is_ipv4 is False self.error_message = error_message def __call__(self, value): diff --git a/gluon/widget.py b/gluon/widget.py index d56be20a..fbd522ae 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -766,7 +766,7 @@ def console(): msg = ('run web2py in interactive shell or IPython (if installed) with ' 'specified appname (if app does not exist it will be created). ' - 'APPNAME like a/c/f (c,f optional)') + 'APPNAME like a/c/f?x=y (c,f and vars x,y optional)') parser.add_option('-S', '--shell', dest='shell', @@ -1076,7 +1076,10 @@ def start_schedulers(options): return # Work around OS X problem: http://bugs.python.org/issue9405 - import urllib + if PY2: + import urllib + else: + import urllib.request as urllib urllib.getproxies() for app in apps: diff --git a/gluon/xmlrpc.py b/gluon/xmlrpc.py index 4957e995..f163b79a 100644 --- a/gluon/xmlrpc.py +++ b/gluon/xmlrpc.py @@ -7,8 +7,12 @@ | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) """ -from SimpleXMLRPCServer import SimpleXMLRPCDispatcher +from gluon._compat import PY2 +if PY2: + from SimpleXMLRPCServer import SimpleXMLRPCDispatcher +else: + from xmlrpc.server import SimpleXMLRPCDispatcher def handler(request, response, methods): response.session_id = None # no sessions for xmlrpc diff --git a/scripts/access.wsgi b/scripts/access.wsgi index 86285070..8cc6b40b 100644 --- a/scripts/access.wsgi +++ b/scripts/access.wsgi @@ -35,11 +35,17 @@ # URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access' +PY2 = sys.version_info[0] == 2 def allow_access(environ,host): + if PY2: + import urllib2 + from urllib import urlencode + else: + from urllib import request as urllib2 + from urllib.parse import urlencode + import os - import urllib - import urllib2 import datetime header = '%s @ %s ' % (datetime.datetime.now(),host) + '='*20 pprint = '\n'.join('%s:%s' % item for item in environ.items()) @@ -56,7 +62,7 @@ def allow_access(environ,host): if key.startswith('HTTP_'): headers[key[5:]] = environ[key] # this passes the cookies through! try: - data = urllib.urlencode({'request_uri':environ['REQUEST_URI']}) + data = urlencode({'request_uri':environ['REQUEST_URI']}) request = urllib2.Request(URL_CHECK_ACCESS % dict(app=app),data,headers) response = urllib2.urlopen(request).read().strip().lower() if response.startswith('true'): return True diff --git a/scripts/check_lang_progress.py b/scripts/check_lang_progress.py new file mode 100644 index 00000000..6c53bbd7 --- /dev/null +++ b/scripts/check_lang_progress.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Written by Vinyl Darkscratch, www.queengoob.org + +import sys +import os + +# TODO: add comments + +# This script can be run with no arguments (which sets the language folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (language folder path and default language). +# When run, this script will compare the original language's strings to their assigned values and determine if they match, just like web2py's interface. While some phrases may be the exact same between both languages, this should provide a good idea of how far along translations are. + +def check_lang_progress(cwd, default_lang): + for x in os.listdir(cwd): + if x == default_lang or x.startswith("plural-"): continue + + data = eval(open(os.path.join(cwd, x)).read()) + + total = 0 + translated = 0 + + for key in data: + total += 1 + if key.replace('@markmin\x01', '') != data[key]: translated += 1 + + print "Translations for %s (%s): %d/%d Translated (%d Untranslated)" %(data['!langname!'], data['!langcode!'], translated, total, total-translated) + +if __name__ == "__main__": + cwd = os.getcwd() + default_lang = 'en' + + if len(sys.argv) > 2: + cwd = sys.argv[1] + default_lang = sys.argv[2] + elif len(sys.argv) > 1: + default_lang = sys.argv[1] + + check_lang_progress(cwd, default_lang) \ No newline at end of file diff --git a/scripts/drop-pgsql-tables.sh b/scripts/drop-pgsql-tables.sh new file mode 100644 index 00000000..44e51972 --- /dev/null +++ b/scripts/drop-pgsql-tables.sh @@ -0,0 +1,14 @@ +# first, login with psql +psql -U USERNAME -d databasename + +# set output for all queries +\o FILENAME.sql + +# run this query +select 'drop table ' || tablename || ' cascade;' from pg_tables; + +# logout from psql +\q + +# run sql script from commandline +psql -U USERNAME -d databasename -f FILENAME.sql \ No newline at end of file diff --git a/scripts/make_min_web2py.py b/scripts/make_min_web2py.py index 2fef642c..587820d6 100644 --- a/scripts/make_min_web2py.py +++ b/scripts/make_min_web2py.py @@ -48,7 +48,7 @@ def main(): global REQUIRED, IGNORED if len(sys.argv) < 2: - print USAGE + print(USAGE) # make target folder target = sys.argv[1] diff --git a/scripts/sessions2trash.py b/scripts/sessions2trash.py index f338b453..228cf73f 100755 --- a/scripts/sessions2trash.py +++ b/scripts/sessions2trash.py @@ -26,6 +26,14 @@ Typical usage: def delete_sessions(): single_loop(auth.settings.expiration) +Command lines options specific to session2trash.py: +NOTE: They should be preceeded by web2py command line option "-A" to be passed to script. + +-f, --force : Ignore session expiration. Force expiry based on -x option or auth.settings.expiration. +-o, --once : Delete sessions, then exit. Essential when trigger trash sessions from system CRON JOB +-s, --sleep : Number of seconds to sleep between executions. Default 300. +-v, --verbose : print verbose output, a second -v increases verbosity +-x, --expiration : Expiration value for sessions without expiration (in seconds) """ from __future__ import with_statement diff --git a/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh b/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh index 9d7fcee8..e34959d3 100644 --- a/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh +++ b/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh @@ -222,7 +222,7 @@ echo < 2: + cwd = sys.argv[1] + default_lang = sys.argv[2] + elif len(sys.argv) > 1: + default_lang = sys.argv[1] + + update_languages(cwd, default_lang) diff --git a/scripts/zip_static_files.py b/scripts/zip_static_files.py index 57054e5b..170ef12a 100644 --- a/scripts/zip_static_files.py +++ b/scripts/zip_static_files.py @@ -14,7 +14,7 @@ def zip_static(filelist=[]): extension = os.path.splitext(fi) extension = len(extension) > 1 and extension[1] or None if not extension or extension not in ALLOWED_EXTS: - print 'skipping %s' % os.path.basename(fi) + print('skipping %s' % os.path.basename(fi)) continue fstats = os.stat(fi) atime, mtime = fstats.st_atime, fstats.st_mtime @@ -23,10 +23,10 @@ def zip_static(filelist=[]): zstats = os.stat(gfi) zatime, zmtime = zstats.st_atime, zstats.st_mtime if zatime == atime and zmtime == mtime: - print 'skipping %s, already gzipped to the latest version' % os.path.basename(fi) + print('skipping %s, already gzipped to the latest version' % os.path.basename(fi)) continue - print 'gzipping %s to %s' % ( - os.path.basename(fi), os.path.basename(gfi)) + print('gzipping %s to %s' % ( + os.path.basename(fi), os.path.basename(gfi))) f_in = open(fi, 'rb') f_out = gzip.open(gfi, 'wb') f_out.writelines(f_in) @@ -36,7 +36,7 @@ def zip_static(filelist=[]): saved = fstats.st_size - os.stat(gfi).st_size tsave += saved - print 'saved %s KB' % (int(tsave) / 1000.0) + print('saved %s KB' % (int(tsave) / 1000.0)) if __name__ == '__main__': ALLOWED_EXTS = ['.css', '.js'] diff --git a/setup.py b/setup.py index cdcf118a..1bb1f3b1 100644 --- a/setup.py +++ b/setup.py @@ -63,8 +63,10 @@ def start(): 'gluon/contrib/pymysql', 'gluon/contrib/pyrtf', 'gluon/contrib/pysimplesoap', - 'gluon/contrib/pg8000', 'gluon/contrib/plural_rules', + 'gluon/contrib/minify', + 'gluon/contrib/pyaes', + 'gluon/contrib/pyuca', 'gluon/tests', ], package_data={'gluon': ['env.tar']},