Compare commits

..

228 Commits

Author SHA1 Message Date
mdipierro c2b6c645ef R-2.1.1 2012-10-15 06:40:55 -05:00
mdipierro 36810aaccc fixed error for missing driver 2012-10-15 06:36:44 -05:00
mdipierro 8e14d1c92e updated changelog 2012-10-15 06:15:33 -05:00
mdipierro 9b7e944dea improved custom_inport with app-level track changes 2012-10-15 05:55:45 -05:00
mdipierro b6429ca5fd better errors in custom_import, thanks Michele 2012-10-15 05:46:57 -05:00
mdipierro 8607b09e64 -Y starts cron 2012-10-15 05:33:21 -05:00
mdipierro 64151a2c3e reverted custom_install tests 2012-10-14 20:07:54 -05:00
mdipierro d5749db0cd cron disabled by default unless -H 2012-10-14 18:29:17 -05:00
mdipierro df7d6847e6 fixed issue 1088 2012-10-14 16:40:49 -05:00
mdipierro c6b5ad8179 auth = Auth(db).define_tables() in one line 2012-10-14 15:55:45 -05:00
mdipierro 71ec500fde changed default driver for sqlite, thanks Niphlod 2012-10-14 15:32:33 -05:00
mdipierro 6cf05edf63 DAL(...,do_connect=False,migrate_enabled=False) 2012-10-14 15:08:25 -05:00
mdipierro 39c5341dbc auth_wiki extra fields, thanks Alan 2012-10-14 14:44:37 -05:00
mdipierro e2234a8771 added storage of length, notnull, unique in .table files 2012-10-14 14:41:28 -05:00
mdipierro cac9741cb7 wiki preview patch, thanks Niphlod 2012-10-14 14:28:17 -05:00
mdipierro 648cb296e6 bettere errors in custom_import, thanks Michele 2012-10-14 14:20:12 -05:00
mdipierro 5a0ebb7d8c faster better web2py_uuid, thanks Michele 2012-10-14 14:16:43 -05:00
mdipierro b7b534ccf2 new tests in test_web, tests static/_1.2.3/... 2012-10-13 14:36:20 -05:00
mdipierro fab23d210b removed un-necessary setup_exe_2.6.py 2012-10-13 10:16:06 -05:00
mdipierro 6a18539f7e no more custom_import_install in gaehandler.py 2012-10-13 09:37:41 -05:00
mdipierro 4d87a90c10 redis session patch, thanks Niphlod 2012-10-13 09:26:53 -05:00
mdipierro 2c9767e500 fixed issue 1081, conflict betweeen web2py css error and bootstrap error classes, thanks Paolo and lapcchan 2012-10-13 09:25:32 -05:00
mdipierro 2b1d9acba5 fixed issue 1084 2012-10-12 22:16:23 -05:00
mdipierro 8a2d18b63b ip address validator fixed 2012-10-12 18:17:06 -05:00
Massimo b59f9f896c fixed problem with renaming of the rewrite.thread to rewrite.THREAD_LOCAL 2012-10-12 12:19:08 -05:00
Massimo 496e6663fd some minor simplifications in rewrite.py 2012-10-12 11:48:25 -05:00
Massimo a4fbf9b6b4 improved speed of regex_url_in 2012-10-12 11:33:02 -05:00
mdipierro b8afce746b much better custom_import, smaller, faster, and finally works with shell 2012-10-11 22:03:20 -05:00
mdipierro 4c239eaf79 fixed cron 2012-10-11 16:48:07 -05:00
mdipierro 1d03233c83 just alignment of custom_import 2012-10-11 09:23:36 -05:00
mdipierro 5358ad271e faster init on gae and better gae_memcache 2012-10-11 08:27:58 -05:00
Massimo 2fa7d72b01 added comments in cache, thanks Michele 2012-10-10 15:16:21 -05:00
mdipierro f034d83c81 fixed problem with DAL on googlesql 2012-10-10 12:20:25 -05:00
mdipierro b3afa2ab57 fixed impval=0, thanks vp 2012-10-10 12:10:51 -05:00
mdipierro 36fc758690 increased admin (is_local) security, thanks Jonathan 2012-10-10 12:00:59 -05:00
mdipierro f764eb2653 rocket passes all pathoc tests without hang 2012-10-10 11:57:30 -05:00
mdipierro 63f56ddecc faster local_hosts lookup 2012-10-10 11:43:06 -05:00
mdipierro 0b2bab3f3d fixed issue 1077, thanks c.guimaraes 2012-10-10 11:25:25 -05:00
mdipierro 66de02929b fixed gae_memcache increment bug, issue 1079, thanks in4tunio 2012-10-10 11:21:01 -05:00
mdipierro 3c4a142f9a many improvements in cache, lazy cache.ram and cache.disk 2012-10-10 11:03:52 -05:00
mdipierro bf6f090428 many improvements in cache 2012-10-10 11:01:09 -05:00
mdipierro 59ab818f5b better toolbar, thanks Niphlod 2012-10-10 09:19:01 -05:00
mdipierro 77847daa77 auth.wiki preview, thanks Niphlod 2012-10-10 09:18:09 -05:00
Massimo 690d2df774 redis_sessions.py, thanks Niphlod 2012-10-09 14:36:34 -05:00
Massimo 643cc9c141 added jquery.mobile 1.2.0 2012-10-09 09:04:27 -05:00
mdipierro 562cb58342 some work on mongodb but references do not work 2012-10-08 22:09:25 -05:00
mdipierro 1c8ef29413 fixed time_expire typo in mamcache, thanks Bruno 2012-10-08 12:09:00 -05:00
mdipierro 8e67c551de fixed problem with close all instances when no instances 2012-10-08 11:03:21 -05:00
mdipierro 03e3324a12 removed rc1 2012-10-08 09:15:33 -05:00
mdipierro a3ba3f662a 2.1.0rc1 2012-10-07 22:45:38 -05:00
mdipierro 2b21678d48 clearing zombie list. Should not be necessary but what the heck 2012-10-07 13:31:02 -05:00
mdipierro 09f29257d2 fixed issue 1074, thanks mjmare 2012-10-07 10:48:37 -05:00
mdipierro 62be48decf fixed issue 1072 2012-10-07 10:13:20 -05:00
mdipierro 952a29d5da fixed routes example, issue 1073 2012-10-07 10:12:04 -05:00
mdipierro db9151b993 fixed routes example, issue 1073 2012-10-07 10:11:10 -05:00
mdipierro e1bb2b4556 db.define_table(...,redefine=True) 2012-10-07 01:10:08 -05:00
mdipierro 668658b7c9 zombie dal is here, should fix scheduler problems while retaining Row/Rows serialization 2012-10-07 01:07:49 -05:00
mdipierro 231a3e1278 Try the mobile interface link in site 2012-10-05 10:38:58 -05:00
mdipierro 41d234bf48 fixed mobile admin, and improved it 2012-10-05 10:24:14 -05:00
mdipierro f16c3a394f lazy tables re-appeared in appadmin 2012-10-05 09:01:51 -05:00
mdipierro 570538042e fixed IMAP reconnect 2012-10-05 07:25:21 -05:00
mdipierro 6a97bfe517 Auth use request.requires_https, thanks Yarin 2012-10-05 07:18:35 -05:00
mdipierro 7b6e5c5270 fixed issue 990 2012-10-04 21:52:04 -05:00
mdipierro 20deb56e33 minor indentation change 2012-10-04 21:45:25 -05:00
mdipierro a23a2c52c2 fixed url_in(request, environ)[1]['PATH_INFO'] 2012-10-04 20:39:00 -05:00
mdipierro 3f6fa941cb fixed issue 1067 2012-10-04 20:36:30 -05:00
mdipierro 28f914463b removed depredation working, thanks Niphlod 2012-10-04 20:17:12 -05:00
mdipierro f0c84b5230 scheduler non-writable field, thanks Niphlod 2012-10-04 20:10:55 -05:00
mdipierro dc51abe54c Auth(secure=True), thanks Yarin and Niphlod 2012-10-04 15:12:52 -05:00
mdipierro f501da74b7 fixed setup, thanks Mike 2012-10-04 15:06:58 -05:00
mdipierro c1d14a35ee fixed preblem with db singledon serialization in tickets 2012-10-04 11:53:04 -05:00
mdipierro 4f4a0318aa fixed wiki for missing body 2012-10-04 10:44:35 -05:00
mdipierro 54a1005af3 fixed problem with __new__ deprecated 2012-10-04 08:44:26 -05:00
mdipierro 7b4fac7a7f allow re-definition of tables 2012-10-04 08:35:39 -05:00
Massimo c80a6db604 better request.requires_https, thanks Yarin and Niphlod 2012-10-03 12:32:12 -05:00
Massimo 96247eda51 fixed some problem with grid 2012-10-03 11:58:50 -05:00
Massimo 433a02537c fixed issue 1064 2012-10-03 11:54:03 -05:00
Massimo bacff3453b fised issue 1063 2012-10-02 14:55:56 -05:00
Massimo b20e74c899 wiki uses contains instead of startswith, thanks David 2012-10-02 14:50:16 -05:00
mdipierro efa5ceb6de fixed issue 738 2012-10-01 21:16:42 -05:00
mdipierro f79660dd2a attempt to fix issue 981 2012-10-01 21:11:45 -05:00
mdipierro 606daabdd3 possibly fixed 1020 2012-10-01 20:52:02 -05:00
mdipierro fc1e4ad620 fixed DAL.__new__ and SQLFORM.factory 2012-10-01 19:15:37 -05:00
mdipierro 54dcf2ead9 more customizaiton of booleans 2012-10-01 17:07:06 -05:00
mdipierro 69cea23cd4 better generic.xml, thanks Derek 2012-10-01 15:54:35 -05:00
mdipierro 083b598c93 fixed issue 1031, thanks David 2012-10-01 14:49:42 -05:00
mdipierro 94f11501ed another rocket fix 2012-10-01 14:25:38 -05:00
mdipierro 9ac4a11db1 fixed textarea for long translation strings in admin 2012-10-01 12:30:02 -05:00
mdipierro a56cf59136 speedup in build-environment, thanks Michele 2012-10-01 12:01:35 -05:00
mdipierro 12cc81147c fixed rocket (removed handling of static files, faster parse headers, pathoc fault-tolerant) 2012-10-01 11:57:45 -05:00
mdipierro f70737ddc2 simplification in after_update 2012-09-30 16:50:57 -05:00
mdipierro 1a22497b64 self.connector 2012-09-30 14:45:47 -05:00
mdipierro 6da330a001 adapter have do_connect 2012-09-30 14:19:52 -05:00
mdipierro f6652e7530 made contrib.pbkdf2 optionional for utils 2012-09-30 13:47:49 -05:00
mdipierro 5148c6ec3a cleaner code in dal 2012-09-30 13:40:41 -05:00
mdipierro 954c79abcb fixed Storage copy 2012-09-30 13:20:05 -05:00
mdipierro 7ec3fe3bfe experimental DAL rewrite (work in progress) 2012-09-30 08:37:27 -05:00
mdipierro 9ce1cde6ca fixed issues with grid form and b.parent 2012-09-30 08:32:03 -05:00
mdipierro 672af73e96 fixed admin and new dal 2012-09-30 07:30:08 -05:00
mdipierro 67022578e3 fixed some bugs with singleton and remobed password from session.auth.user 2012-09-30 07:26:23 -05:00
mdipierro 65fe4c16fc fixed problem with order of lazyness in DAL serialization in session 2012-09-30 00:23:15 -05:00
mdipierro b6f9cd1863 login works fine with lazy DAL 2012-09-30 00:10:25 -05:00
mdipierro 4908a5e7be reverted DAL singleton until better understood 2012-09-29 23:47:30 -05:00
mdipierro 853c0b1a3a Better LazySet 2012-09-29 23:38:07 -05:00
mdipierro d0d8a4827e pickle.dumps(db(query).select() now always works and loads restore the full object (almost) 2012-09-29 23:20:37 -05:00
mdipierro 4745ce42f9 DAL is now a pickeable singleton 2012-09-29 22:33:18 -05:00
mdipierro bba13becd0 in dal renamed thread to thread_local 2012-09-29 20:59:50 -05:00
mdipierro 7712e005c4 MemcacheClient(time_expire=3600) 2012-09-29 20:20:03 -05:00
mdipierro 07c667f469 better rocket parsing, support for multiline headers 2012-09-29 18:39:53 -05:00
mdipierro 57ad1ec1cf made new web2py_uuid working on python 2.5 2012-09-29 18:35:09 -05:00
mdipierro 60a043479c yet faster web2py_uuid by caching urandom results 2012-09-29 17:56:43 -05:00
mdipierro 1a43e1671f more web2py_uuid speedup, thanks Michele 2012-09-29 17:16:35 -05:00
mdipierro cd955d3603 fixed issue 1046, thanks Houdini 2012-09-29 10:17:06 -05:00
mdipierro 492a0c51d1 fixed copy.copy(storage), thanks Corne 2012-09-29 09:55:57 -05:00
mdipierro dd4a4c281f prettidate works with future dates, thanks Sanjoy 2012-09-29 09:53:46 -05:00
mdipierro 41ec6da72c fixed issue 1044, thanks Paolo 2012-09-28 20:20:05 -05:00
mdipierro e18f38d4da static asset management, thanks Niphlod 2012-09-28 17:06:11 -05:00
Massimo bd27df8fa5 fixed issue 1042, thanks Dominic 2012-09-28 12:47:36 -05:00
Massimo 2f44c2de9b closed issue 995, thanks Peter 2012-09-28 12:38:03 -05:00
mdipierro ccf154a807 fixed issue 1040 2012-09-28 09:25:27 -05:00
mdipierro 837453dd6e faster web2py_uuid() thanks Michele 2012-09-28 09:21:03 -05:00
mdipierro 64bc60cdf0 fixed search in wiki again for firebird 2012-09-28 08:51:18 -05:00
mdipierro af258c334e fixed menu hoover issue 1036, thanks Paolo 2012-09-27 22:28:59 -05:00
mdipierro 627fff624a fixed many bugs in dal, thanks Mart Senecal 2012-09-27 22:15:56 -05:00
mdipierro bc8127f6de fixed problem with pypy 2012-09-27 22:09:23 -05:00
mdipierro 19bf43815d added better comments to address issue 1033 2012-09-27 21:45:19 -05:00
mdipierro 7709074d7c fixed field_parent references broken in 2.0.x, thanks Marin 2012-09-27 15:12:36 -05:00
mdipierro 4b73f249dd removed some un-necessary has_attr 2012-09-27 14:43:57 -05:00
mdipierro ae9e2c2be0 fixed add button in grid 2012-09-27 14:21:55 -05:00
mdipierro fd8edb5aa2 fixed issue 1032, thanks Carlos 2012-09-27 14:09:05 -05:00
mdipierro 82ab59c46a http 422, better tagcloud, possibly fixed firbird issue with tagcloud 2012-09-27 14:05:43 -05:00
mdipierro 74a63e98b5 possibly fixed issue 1031, thanks villas 2012-09-26 12:45:56 -05:00
Massimo 9e23b3dac5 db.get(,default=None), thanks Carlos 2012-09-25 16:18:52 -05:00
Massimo 077ce011e2 distinct instead of groupby 2012-09-25 14:56:49 -05:00
Massimo 1430bc824f test 2012-09-25 14:50:32 -05:00
mdipierro 722b16e620 CAS should disabled retrive username 2012-09-24 22:44:19 -05:00
mdipierro 30727ef9e4 synced web2py.js, thanks Jeremy 2012-09-24 20:18:30 -05:00
mdipierro 0f322f8a69 db().select(...query.case('true','false)) code cleanup 2012-09-24 18:09:22 -05:00
mdipierro ec5b4dde6f db().select(...query.case('true','false)) 2012-09-24 18:06:07 -05:00
mdipierro 630dcb799e gluon/contrib/comet_messaging.py -> gluon/contrib/websocket_messaging.py 2012-09-24 16:21:11 -05:00
mdipierro c6d1e47226 nore more typo fixed, thanks niphlod 2012-09-24 15:54:58 -05:00
mdipierro aa8315a7c9 data, no attr, thanks Niphlod 2012-09-24 15:13:34 -05:00
mdipierro 80040bf8e5 apos entity fix, thanks Michele 2012-09-24 12:48:39 -05:00
mdipierro 8f382f322e better setup-web2py-nginx-uwsgi-ubuntu.sh, thanks Bruno 2012-09-24 12:44:42 -05:00
mdipierro 9ccc7dc59a crud methods have FORM **attributes, thanks Daniel 2012-09-24 12:43:22 -05:00
mdipierro 70f6f86827 fixed https://github.com/web2py/web2py/pull/33, fetching Oracle Clobs, thanks dhx 2012-09-24 12:39:28 -05:00
mdipierro 3055c87567 fixed issue 1028, thanks Sherdim 2012-09-24 11:59:57 -05:00
mdipierro 473955611a addClass/hasClass entropy_check, thanks Niphlod 2012-09-24 11:50:59 -05:00
mdipierro 21a7ded8e0 fixed minify code to fix paths in css files, thanks Jeremy 2012-09-24 10:11:35 -05:00
mdipierro 23347995e2 css:inline patch,thanks Jeremy 2012-09-24 09:25:26 -05:00
mdipierro 4027a3cda3 SQLFORM().accepts(onvalidation=dict(onchange=lambda....)), thanks Alan 2012-09-23 19:24:05 -05:00
mdipierro a9b6c4f1a3 linked_tables in smartgrid can be a dict() 2012-09-23 19:16:27 -05:00
mdipierro c1330662aa better entropy colors and optional callback, thanks Niphlod 2012-09-23 17:00:34 -05:00
mdipierro 3e38f8017f better entropy colors and optional callback, thanks Niphlod 2012-09-23 16:58:11 -05:00
mdipierro 41bc1cb1e9 linked_tables in smartgrid can be a dict() 2012-09-23 16:31:44 -05:00
mdipierro 9fb94008ea linked_tables in smartgrid can be a dict() 2012-09-23 16:30:31 -05:00
mdipierro 5f647a46b8 moved layout.js in web2py_bootstrap.js and include_files(extensions=[...]), thanks Jeremy 2012-09-23 14:37:50 -05:00
mdipierro 69a2e76c3c entropy check code in web2py.js, thanks Niphlod 2012-09-23 14:07:50 -05:00
mdipierro 4d6598c645 fixed typo in validators 2012-09-22 11:19:14 -05:00
mdipierro 56acb685e9 removed some duplication of code in dal.py, thanks Mart 2012-09-22 10:11:17 -05:00
mdipierro f0be4416b3 restored comments in boostrap_css.css 2012-09-22 09:54:53 -05:00
mdipierro 93ce59f65b moved entropy js back to user.html 2012-09-22 09:52:08 -05:00
mdipierro 28b94eceb2 formstyle='inline' for forms with single field 2012-09-21 22:49:45 -05:00
mdipierro e4c63769f2 reverted 4077 because distinct on and groupby are not equivalent. Moreover distinct on is supported only by postgresql because potentially non-deterministic and should be discouraged. Thanks Alexander 2012-09-21 22:29:55 -05:00
mdipierro e0fc61932d broken long lines in minify 2012-09-21 22:23:45 -05:00
mdipierro 3de3046260 fixed share Google+, thanks nicozanf 2012-09-21 22:14:08 -05:00
mdipierro b6a96301cd Merge branch 'master' of https://github.com/nicozanf/web2py 2012-09-21 22:13:09 -05:00
mdipierro 480dea23ad moved entropy JS in PasswordWidget but the source should be moved in separate file 2012-09-21 22:12:17 -05:00
mdipierro 457ef24c04 added missing patch, thanks Niphlod 2012-09-21 22:01:51 -05:00
Nico Zanferrari dbf6ce6d6c Modified share to Buzz to Google+ 2012-09-22 00:10:23 +02:00
nicozanf 7686a6fc93 Merge pull request #3 from mdipierro/master
up
2012-09-21 14:32:55 -07:00
mdipierro afde6efafa cleanup web2py_bootstrap.css 2012-09-21 09:37:37 -05:00
mdipierro af53f17f5a scheduler optimization for single process, thanks Niphlod 2012-09-21 08:58:43 -05:00
mdipierro 8f9cf4034d user.html has entrpy color-coding, thanks Niphlod 2012-09-21 08:57:11 -05:00
mdipierro 31a6c5df9b hidden fields should be visible 2012-09-21 08:29:16 -05:00
nicozanf 08d1b26738 Merge pull request #1 from mdipierro/master
test
2012-09-20 13:29:35 -07:00
mdipierro abd739123c added search to codemirror 2012-09-20 13:25:54 -05:00
mdipierro da8570b560 better zip_static_files.py, thanks Niphlod 2012-09-20 11:19:11 -05:00
mdipierro 8d238890a2 fixed issue 1021, thanks Edouard and Jonathan 2012-09-20 11:16:51 -05:00
nicozanf ca29f262e5 Unnecessary calls to logging()
the module logging() is not imported and called unnecessary.
2012-09-20 17:52:55 +03:00
mdipierro cffefbeb2e possibly fixed issue 1020 2012-09-20 09:27:05 -05:00
mdipierro 52528f9311 changed brand color 2012-09-20 09:24:38 -05:00
mdipierro 8b75256281 better layout yet, thanks Paolo 2012-09-20 09:19:21 -05:00
mdipierro 2385ed22b1 new web2py_bootstrap_nojs.css, thanks Paolo 2012-09-20 09:16:04 -05:00
mdipierro 7a0cf446ba entropy computation in IS_STRONG, thanks Jonathan 2012-09-20 09:14:51 -05:00
mdipierro 586d85cf08 fixed a problem with reset_password redirect loop 2012-09-19 23:04:10 -05:00
mdipierro f371e72e07 mail.send(...,sender='Mr X <%(sender)s>') 2012-09-19 00:03:06 -05:00
mdipierro 8d9bc1cb53 mail.send(...,sender='Mr X <%(sender)s>') 2012-09-19 00:00:18 -05:00
mdipierro 29edf94498 enhanced imapadapter, thanks Alan 2012-09-18 13:39:16 -05:00
mdipierro d7a108ff06 fixed recent typo, thanks Christian 2012-09-18 13:37:41 -05:00
mdipierro 5ed41285c0 possibly fixed issue 1016 2012-09-18 13:35:47 -05:00
mdipierro b16d79e654 some more CSS fixes, thanks Paolo 2012-09-17 21:37:13 -05:00
mdipierro 90e5d06d3d bootstrap 2.1.1, thanks Paolo 2012-09-17 21:32:49 -05:00
mdipierro a1156cae6c improved minify, thanks Niphlod 2012-09-17 21:25:05 -05:00
mdipierro 037a200357 IS_IN_DB cacheable=True 2012-09-17 16:22:30 -05:00
mdipierro e282454bfe fixed a problem with order of table definitions in auth.wiki, thanks David 2012-09-17 14:23:13 -05:00
mdipierro e499c1d5a4 fixed problem with missing wiki_media.filename 2012-09-17 11:47:07 -05:00
mdipierro 8540b1b113 fixed issue 1014, thanks Sherdim 2012-09-17 11:43:26 -05:00
mdipierro 6b5e3f2abf better layout, thanks Paolo 2012-09-17 11:36:00 -05:00
mdipierro f8be550e6a fixed comment 2012-09-17 10:26:17 -05:00
mdipierro 6a7b04199f fixed issue 1005, thanks howesc 2012-09-17 09:36:13 -05:00
mdipierro 2847e0f4df fixed imap dal __contains__ issue 1013, thanks Alan 2012-09-17 09:12:13 -05:00
mdipierro eec7f7c687 cid argument in SQLTABLE, thanks Massimiliano 2012-09-17 08:58:01 -05:00
mdipierro a4253deda3 fixed ducktyping problem, thanks Jonathan 2012-09-17 07:13:26 -05:00
mdipierro 372d639601 fixed exception on missin stored_password 2012-09-17 07:10:51 -05:00
mdipierro 06e5c6869d scripts/zip_static_files.py, thanks Niphlod 2012-09-16 15:55:10 -05:00
mdipierro 933189c216 niphlod is right, no need to to restrict gzip streaming to rocket 2012-09-16 15:54:20 -05:00
mdipierro c942881f36 convert distinct to groupby when not supported 2012-09-16 15:51:51 -05:00
mdipierro 99e491ca0f better streamer 2012-09-16 15:38:31 -05:00
mdipierro ea539d50e2 adding missing import in scheduler 2012-09-16 14:10:39 -05:00
mdipierro 497bbf002c possibly fixed issue 1011 2012-09-16 13:55:29 -05:00
mdipierro 2629fac624 fixed problem with error on liststring widget 2012-09-16 13:06:43 -05:00
mdipierro 30af5901eb fixed alignment in forms with list:string 2012-09-16 12:23:19 -05:00
mdipierro 42b60d3291 fixed datetime T in scheduler 2012-09-16 11:46:03 -05:00
mdipierro 9b53fd2302 fixed issue 1010, error reporting on list:string 2012-09-16 11:43:03 -05:00
mdipierro 972f64b6f9 better impersonate, thanks Alan 2012-09-16 10:43:39 -05:00
mdipierro 3eb4627ccf typo in new appadmin.html 2012-09-16 08:53:01 -05:00
mdipierro 2ba88b8951 auth.wiki(render='markmin'), thanks Guruyaya 2012-09-15 15:42:44 -05:00
mdipierro 117b04169c wiki file should be filename, thanks David 2012-09-15 13:41:24 -05:00
mdipierro c56d5bd066 gae_memcache.clear(), thanks Matt 2012-09-14 21:17:01 -05:00
mdipierro 00bfde9ce9 cache.with_prefix(cache.ram,'prefix'), thanks Bruno 2012-09-14 16:08:25 -05:00
mdipierro 5f503d0427 db.t.f.epoch(), thanks Niphlod 2012-09-14 15:51:58 -05:00
mdipierro 47bcab6b26 fixed google+ share 2012-09-14 09:42:16 -05:00
mdipierro ecca0439f1 db(table).select() uses table._id<>None instead of table._id>0 2012-09-14 09:27:09 -05:00
mdipierro e896be4037 fixed typo in setup script, thanks Devon 2012-09-14 09:23:31 -05:00
mdipierro b0e4284130 uninstalled apps backup under deposit 2012-09-14 08:47:42 -05:00
mdipierro 3e69889cca removed troublesome chipin widget 2012-09-13 21:48:40 -05:00
mdipierro dfb2e62c6c tables and field names should be allowed to start in a number, thanks Adi 2012-09-13 21:04:45 -05:00
105 changed files with 3078 additions and 2672 deletions
+34 -1
View File
@@ -1,4 +1,37 @@
## 2.00.1
## 2.1.0
- overall faster web2py
- when apps are deleted, a w2p copy left in deposit folder
- change in cron (it is now disabled by default). removed -N option and introduced -Y.
- faster web2py_uuid() and request initialization logic, thanks Michele
- static asset management, thanks Niphlod
- improved mobile admin
- request.requires_https and Auth(secure=True), thanks Yarin and Niphlod
- better custom_import (works per app and is faster), thanks Michele
- redis_sesssion.py, thanks Niphlod
- allow entropy computation in IS_STRONG and web2py.js, thanks Jonathan and Niphlod
- fixed many aith.wiki problems
- support for auth.wiki(render='html')
- better welcome layout, thanks Paolo
- db.define_table(...,redefine=True)
- DAL, Row, and Rows object can now be pickled/unpickled, thanks to zombie DAL.
- admin uses codemirror
- allow syntax auth = Auth(db).define_tables()
- better auth.wiki with preview, thanks Alan
- better auth.impersonate, thanks Alan
- upgraded jQuery 1.8
- upgraded Bootstrap 2.1
- fixed problem with dropbox_account.py
- many fixes to cache.ram, cache.disk, memcache and gae_memcache
- cache.with_prefix(cache.ram,'prefix')
- db.table.field.epoch() counts seconds from epoch
- DAL support for SQL CASE, example: db().select(...query.case('true','false))
- DAL(...,do_connect=False) allows faking connections
- DAL(...,auto_import=True) now retieves some fiel attributes
- mail can specify a sender: mail.send(...,sender='Mr X <%(sender)s>')
- renamed gluon/contrib/comet_messaging.py -> gluon/contrib/websocket_messaging.py
## 2.0.1-11
### DAL Improvements
+8 -5
View File
@@ -29,7 +29,7 @@ update:
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked"
src:
echo 'Version 2.0.9 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
echo 'Version 2.1.1 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
@@ -107,10 +107,6 @@ win:
cp applications/__init__.py ../web2py_win/web2py/applications
cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip .
pip:
# create Web2py distribution for upload to Pypi
# after upload clean Web2py sources with rm -R ./dist
python setup.py sdist
run:
python2.5 web2py.py -a hello
commit:
@@ -129,3 +125,10 @@ tag:
hg tag -l '$(S)'
make commit S='$(S)'
make push
pip:
# create Web2py distribution for upload to Pypi
# after upload clean Web2py sources with rm -R ./dist
# http://guide.python-distribute.org/creation.html
python setup.py sdist
python setup.py register
python setup.py sdist upload
+1 -1
View File
@@ -1 +1 @@
Version 2.0.9 (2012-09-13 17:48:15) stable
Version 2.1.1 (2012-10-15 06:40:52) stable
-1
View File
@@ -80,7 +80,6 @@ skip_files: |
builtins:
- remote_api: on
# - datastore_admin: on
- appstats: on
- admin_redirect: on
- deferred: on
+4 -1
View File
@@ -113,7 +113,6 @@ def query_by_table_type(tablename,db,request=request):
# ## list all databases and tables
# ###########################################################
def index():
return dict(databases=databases)
@@ -207,6 +206,7 @@ def select():
if match:
table = match.group('table')
try:
tb = None
nrows = db(query).count()
if form.vars.update_check and form.vars.update_fields:
db(query).update(**eval_in_global_env('dict(%s)'
@@ -221,6 +221,8 @@ def select():
else:
rows = db(query,ignore_common_filters=True).select(limitby=(start, stop))
except Exception, e:
import traceback
tb = traceback.format_exc()
(rows, nrows) = ([], 0)
response.flash = DIV(T('Invalid Query'),PRE(str(e)))
# begin handle upload csv
@@ -250,6 +252,7 @@ def select():
rows=rows,
query=request.vars.query,
formcsv = formcsv,
tb = tb,
)
+9 -9
View File
@@ -3,7 +3,6 @@
EXPERIMENTAL_STUFF = True
if EXPERIMENTAL_STUFF:
is_mobile = request.user_agent().is_mobile
if is_mobile:
response.view = response.view.replace('default/','default.mobile/')
response.menu = []
@@ -366,10 +365,15 @@ def uninstall():
else:
session.flash = T('no permission to uninstall "%s"', app)
redirect(URL('site'))
if app_uninstall(app, request):
session.flash = T('application "%s" uninstalled', app)
else:
try:
filename = app_pack(app, request, raise_ex=True)
except:
session.flash = T('unable to uninstall "%s"', app)
else:
if app_uninstall(app, request):
session.flash = T('application "%s" uninstalled', app)
else:
session.flash = T('unable to uninstall "%s"', app)
redirect(URL('site'))
return dict(app=app, dialog=dialog)
@@ -782,7 +786,7 @@ def edit_language():
_class='untranslated' if k==s else 'translated'
if len(key) <= 40:
if len(s) <= 40:
elem = INPUT(_type='text', _name=name, value=s,
_size=70,_class=_class)
else:
@@ -1647,7 +1651,6 @@ def git_pull():
session.flash = T("Application updated via git pull")
redirect(URL('site'))
except CheckoutError, message:
logging.error(message)
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
redirect(URL('site'))
except UnmergedEntriesError:
@@ -1657,11 +1660,9 @@ def git_pull():
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
except GitCommandError, status:
logging.error(str(status))
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
except Exception,e:
logging.error("Unexpected error:", sys.exc_info()[0])
session.flash = T("Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
elif 'cancel' in request.vars:
@@ -1693,7 +1694,6 @@ def git_push():
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
redirect(URL('site'))
except Exception, e:
logging.error("Unexpected error:", sys.exc_info()[0])
session.flash = T("Push failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
return dict(app=app,form=form)
+1 -1
View File
@@ -39,7 +39,7 @@ def deploy():
form = SQLFORM.factory(
Field('appcfg',default=GAE_APPCFG,label=T('Path to appcfg.py'),
requires=EXISTS(error_message=T('file not found'))),
Field('google_application_id',requires=IS_ALPHANUMERIC(),label=T('Google Application Id')),
Field('google_application_id',requires=IS_MATCH('[\w\-]+'),label=T('Google Application Id')),
Field('applications','list:string',
requires=IS_IN_SET(apps,multiple=True),
label=T('web2py apps to deploy')),
+183 -125
View File
@@ -3,143 +3,26 @@
'!langcode!': 'it',
'!langname!': 'Italiano',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" è un\'espressione opzionale come "campo1=\'nuovo valore\'". Non si può fare "update" o "delete" dei risultati di un JOIN ',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'%s %%{row} deleted': '%s righe ("record") cancellate',
'%s %%{row} updated': '%s righe ("record") modificate',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'(requires internet access)': '(requires internet access)',
'(something like "it-it")': '(qualcosa simile a "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available: %s': 'È disponibile una nuova versione di web2py: %s',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': "ATTENZIONE: L'accesso richiede una connessione sicura (HTTPS) o l'esecuzione di web2py in locale (connessione su localhost)",
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTZIONE: NON ESEGUIRE PIÙ TEST IN PARALLELO (I TEST NON SONO "THREAD SAFE")',
'ATTENTION: you cannot edit the running application!': "ATTENZIONE: non puoi modificare l'applicazione correntemente in uso ",
'About': 'informazioni',
'About application': "Informazioni sull'applicazione",
'Admin is disabled because insecure channel': 'amministrazione disabilitata: comunicazione non sicura',
'Admin language': 'Admin language',
'Administrator Password:': 'Password Amministratore:',
'Application name:': 'Application name:',
'Are you sure you want to delete file "%s"?': 'Confermi di voler cancellare il file "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Confermi di voler cancellare il plugin "%s"?',
'Are you sure you want to uninstall application "%s"?': 'Confermi di voler disinstallare l\'applicazione "%s"?',
'Are you sure you want to upgrade web2py now?': 'Confermi di voler aggiornare web2py ora?',
'Available databases and tables': 'Database e tabelle disponibili',
'Cannot be empty': 'Non può essere vuoto',
'Cannot compile: there are errors in your app:': "Compilazione fallita: ci sono errori nell'applicazione.",
'Change admin password': 'change admin password',
'Check for upgrades': 'check for upgrades',
'Check to delete': 'Seleziona per cancellare',
'Checking for upgrades...': 'Controllo aggiornamenti in corso...',
'Clean': 'pulisci',
'Compile': 'compila',
'Controller': 'Controller',
'Controllers': 'Controllers',
'Copyright': 'Copyright',
'Create': 'crea',
'Create new simple application': 'Crea nuova applicazione',
'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente',
'DB Model': 'Modello di DB',
'Database': 'Database',
'Date and Time': 'Data and Ora',
'Delete': 'Cancella',
'Delete:': 'Cancella:',
'Deploy': 'deploy',
'Deploy on Google App Engine': 'Installa su Google App Engine',
'EDIT': 'MODIFICA',
'Edit': 'modifica',
'Edit This App': 'Modifica questa applicazione',
'Edit application': 'Modifica applicazione',
'Edit current record': 'Modifica record corrente',
'Editing Language file': 'Modifica file linguaggio',
'Editing file "%s"': 'Modifica del file "%s"',
'Enterprise Web Framework': 'Enterprise Web Framework',
'Error logs for "%(app)s"': 'Log degli errori per "%(app)s"',
'Errors': 'errori',
'Exception instance attributes': 'Exception instance attributes',
'Functions with no doctests will result in [passed] tests.': 'I test delle funzioni senza "doctests" risulteranno sempre [passed].',
'Hello World': 'Salve Mondo',
'Help': 'aiuto',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'Import/Export': 'Importa/Esporta',
'Index': 'Indice',
'Install': 'installa',
'Installed applications': 'Applicazioni installate',
'Internal State': 'Stato interno',
'Invalid Query': 'Richiesta (query) non valida',
'Invalid action': 'Azione non valida',
'Language files (static strings) updated': 'Linguaggi (documenti con stringhe statiche) aggiornati',
'Languages': 'Linguaggi',
'Last saved on:': 'Ultimo salvataggio:',
'Layout': 'Layout',
'License for': 'Licenza relativa a',
'Login': 'Accesso',
'Login to the Administrative Interface': "Accesso all'interfaccia amministrativa",
'Logout': 'uscita',
'Main Menu': 'Menu principale',
'Menu Model': 'Menu Modelli',
'Models': 'Modelli',
'Modules': 'Moduli',
'NO': 'NO',
'New Record': 'Nuovo elemento (record)',
'New application wizard': 'New application wizard',
'New simple application': 'New simple application',
'No databases in this application': 'Nessun database presente in questa applicazione',
'Original/Translation': 'Originale/Traduzione',
'Overwrite installed app': 'sovrascrivi applicazione installata',
'PAM authenticated user, cannot change password here': 'utente autenticato tramite PAM, impossibile modificare password qui',
'Pack all': 'crea pacchetto',
'Pack compiled': 'crea pacchetto del codice compilato',
'Peeking at file': 'Uno sguardo al file',
'Plugin "%s" in application': 'Plugin "%s" nell\'applicazione',
'Plugins': 'I Plugins',
'Powered by': 'Powered by',
'Query:': 'Richiesta (query):',
'Remove compiled': 'rimozione codice compilato',
'Resolve Conflict file': 'File di risoluzione conflitto',
'Rows in table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate',
'Saved file hash:': 'Hash del file salvato:',
'Site': 'sito',
'Start wizard': 'start wizard',
'Static files': 'Files statici',
'Stylesheet': 'Foglio di stile (stylesheet)',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
'TM': 'TM',
'Testing application': 'Test applicazione in corsg',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
'There are no controllers': 'Non ci sono controller',
'There are no models': 'Non ci sono modelli',
'There are no modules': 'Non ci sono moduli',
'There are no static files': 'Non ci sono file statici',
'There are no translators, only default language is supported': 'Non ci sono traduzioni, viene solo supportato il linguaggio di base',
'There are no views': 'Non ci sono viste ("view")',
'This is the %(filename)s template': 'Questo è il template %(filename)s',
'Ticket': 'Ticket',
'To create a plugin, name a file/folder plugin_[name]': 'Per creare un plugin, chiamare un file o cartella plugin_[nome]',
'Unable to check for upgrades': 'Impossibile controllare presenza di aggiornamenti',
'Unable to download app because:': 'Impossibile scaricare applicazione perché',
'Unable to download because': 'Impossibile scaricare perché',
'Unable to download because:': 'Unable to download because:',
'Uninstall': 'disinstalla',
'Update:': 'Aggiorna:',
'Upload & install packed application': 'Carica ed installa pacchetto con applicazione',
'Upload a package:': 'Upload a package:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
'Use an url:': 'Use an url:',
'Version': 'Versione',
'View': 'Vista',
'Views': 'viste',
'Welcome %s': 'Benvenuto %s',
'Welcome to web2py': 'Benvenuto su web2py',
'YES': 'SI',
'additional code for your application': 'righe di codice aggiuntive per la tua applicazione',
'Additional code for your application': 'Additional code for your application',
'admin disabled because no admin password': 'amministrazione disabilitata per mancanza di password amministrativa',
'admin disabled because not supported on google app engine': 'amministrazione non supportata da Google Apps Engine',
'admin disabled because unable to access password file': 'amministrazione disabilitata per impossibilità di leggere il file delle password',
'Admin is disabled because insecure channel': 'amministrazione disabilitata: comunicazione non sicura',
'Admin language': 'Admin language',
'administrative interface': 'administrative interface',
'Administrator Password:': 'Password Amministratore:',
'and rename it (required):': 'e rinominala (obbligatorio):',
'and rename it:': 'e rinominala:',
'appadmin': 'appadmin ',
@@ -147,44 +30,104 @@
'application "%s" uninstalled': 'applicazione "%s" disinstallata',
'application compiled': 'applicazione compilata',
'application is compiled and cannot be designed': "l'applicazione è compilata e non si può modificare",
'Application name:': 'Application name:',
'are not used': 'are not used',
'are not used yet': 'are not used yet',
'Are you sure you want to delete file "%s"?': 'Confermi di voler cancellare il file "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Confermi di voler cancellare il plugin "%s"?',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'Are you sure you want to uninstall application "%s"?': 'Confermi di voler disinstallare l\'applicazione "%s"?',
'Are you sure you want to upgrade web2py now?': 'Confermi di voler aggiornare web2py ora?',
'arguments': 'arguments',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': "ATTENZIONE: L'accesso richiede una connessione sicura (HTTPS) o l'esecuzione di web2py in locale (connessione su localhost)",
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTZIONE: NON ESEGUIRE PIÙ TEST IN PARALLELO (I TEST NON SONO "THREAD SAFE")',
'ATTENTION: you cannot edit the running application!': "ATTENZIONE: non puoi modificare l'applicazione correntemente in uso ",
'Available databases and tables': 'Database e tabelle disponibili',
'back': 'indietro',
'cache': 'cache',
'cache, errors and sessions cleaned': 'pulitura cache, errori and sessioni ',
'can be a git repo': 'can be a git repo',
'Cannot be empty': 'Non può essere vuoto',
'Cannot compile: there are errors in your app:': "Compilazione fallita: ci sono errori nell'applicazione.",
'cannot create file': 'impossibile creare il file',
'cannot upload file "%(filename)s"': 'impossibile caricare il file "%(filename)s"',
'Change admin password': 'change admin password',
'change password': 'cambia password',
'check all': 'controlla tutto',
'Check for upgrades': 'check for upgrades',
'Check to delete': 'Seleziona per cancellare',
'Checking for upgrades...': 'Controllo aggiornamenti in corso...',
'Clean': 'pulisci',
'click here for online examples': 'clicca per vedere gli esempi',
'click here for the administrative interface': "clicca per l'interfaccia amministrativa",
'click to check for upgrades': 'clicca per controllare presenza di aggiornamenti',
'code': 'code',
'collapse/expand all': 'collapse/expand all',
'Compile': 'compila',
'compiled application removed': "rimosso il codice compilato dell'applicazione",
'Controller': 'Controller',
'Controllers': 'Controllers',
'controllers': 'controllers',
'Copyright': 'Copyright',
'Create': 'crea',
'create file with filename:': 'crea un file col nome:',
'create new application:': 'create new application:',
'Create new simple application': 'Crea nuova applicazione',
'created by': 'creato da',
'crontab': 'crontab',
'Current request': 'Richiesta (request) corrente',
'Current response': 'Risposta (response) corrente',
'Current session': 'Sessione (session) corrente',
'currently running': 'currently running',
'currently saved or': 'attualmente salvato o',
'customize me!': 'Personalizzami!',
'data uploaded': 'dati caricati',
'Database': 'Database',
'database': 'database',
'database %s select': 'database %s select',
'database administration': 'amministrazione database',
'Date and Time': 'Data and Ora',
'db': 'db',
'DB Model': 'Modello di DB',
'Debug': 'Debug',
'defines tables': 'defininisce le tabelle',
'Delete': 'Cancella',
'delete': 'Cancella',
'delete all checked': 'cancella tutti i selezionati',
'delete plugin': 'cancella plugin',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Delete:': 'Cancella:',
'Deploy': 'deploy',
'Deploy on Google App Engine': 'Installa su Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'design': 'progetta',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direction: ltr',
'Disable': 'Disable',
'docs': 'docs',
'done!': 'fatto!',
'download layouts': 'download layouts',
'download plugins': 'download plugins',
'EDIT': 'MODIFICA',
'Edit': 'modifica',
'Edit application': 'Modifica applicazione',
'edit controller': 'modifica controller',
'Edit current record': 'Modifica record corrente',
'edit profile': 'modifica profilo',
'Edit This App': 'Modifica questa applicazione',
'edit views:': 'modifica viste (view):',
'Editing file "%s"': 'Modifica del file "%s"',
'Editing Language file': 'Modifica file linguaggio',
'Enterprise Web Framework': 'Enterprise Web Framework',
'Error logs for "%(app)s"': 'Log degli errori per "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'errori',
'Exception instance attributes': 'Exception instance attributes',
'Expand Abbreviation': 'Expand Abbreviation',
'export as csv file': 'esporta come file CSV',
'exposes': 'espone',
'exposes:': 'exposes:',
'extends': 'estende',
'failed to reload module because:': 'ricaricamento modulo fallito perché:',
'file "%(filename)s" created': 'creato il file "%(filename)s"',
@@ -195,73 +138,188 @@
'file does not exist': 'file inesistente',
'file saved on %(time)s': "file salvato nell'istante %(time)s",
'file saved on %s': 'file salvato: %s',
'filter': 'filter',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'I test delle funzioni senza "doctests" risulteranno sempre [passed].',
'Get from URL:': 'Get from URL:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Hello World': 'Salve Mondo',
'Help': 'aiuto',
'htmledit': 'modifica come html',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'Import/Export': 'Importa/Esporta',
'includes': 'include',
'Index': 'Indice',
'insert new': 'inserisci nuovo',
'insert new %s': 'inserisci nuovo %s',
'inspect attributes': 'inspect attributes',
'Install': 'installa',
'Installed applications': 'Applicazioni installate',
'internal error': 'errore interno',
'Internal State': 'Stato interno',
'Invalid action': 'Azione non valida',
'invalid password': 'password non valida',
'Invalid Query': 'Richiesta (query) non valida',
'invalid request': 'richiesta non valida',
'invalid ticket': 'ticket non valido',
'Key bindings': 'Key bindings',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'language file "%(filename)s" created/updated': 'file linguaggio "%(filename)s" creato/aggiornato',
'Language files (static strings) updated': 'Linguaggi (documenti con stringhe statiche) aggiornati',
'languages': 'linguaggi',
'Languages': 'Linguaggi',
'Last saved on:': 'Ultimo salvataggio:',
'Layout': 'Layout',
'License for': 'Licenza relativa a',
'loading...': 'caricamento...',
'locals': 'locals',
'login': 'accesso',
'Login': 'Accesso',
'Login to the Administrative Interface': "Accesso all'interfaccia amministrativa",
'Logout': 'uscita',
'Main Menu': 'Menu principale',
'Menu Model': 'Menu Modelli',
'merge': 'unisci',
'models': 'modelli',
'Models': 'Modelli',
'Modules': 'Moduli',
'modules': 'moduli',
'new application "%s" created': 'creata la nuova applicazione "%s"',
'New application wizard': 'New application wizard',
'new plugin installed': 'installato nuovo plugin',
'New Record': 'Nuovo elemento (record)',
'new record inserted': 'nuovo record inserito',
'New simple application': 'New simple application',
'next 100 rows': 'prossime 100 righe',
'NO': 'NO',
'No databases in this application': 'Nessun database presente in questa applicazione',
'no match': 'nessuna corrispondenza',
'or import from csv file': 'oppure importa da file CSV',
'or provide app url:': "oppure fornisci url dell'applicazione:",
'Original/Translation': 'Originale/Traduzione',
'Overwrite installed app': 'sovrascrivi applicazione installata',
'Pack all': 'crea pacchetto',
'Pack compiled': 'crea pacchetto del codice compilato',
'pack plugin': 'crea pacchetto del plugin',
'PAM authenticated user, cannot change password here': 'utente autenticato tramite PAM, impossibile modificare password qui',
'password changed': 'password modificata',
'Peeking at file': 'Uno sguardo al file',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" cancellato',
'Plugin "%s" in application': 'Plugin "%s" nell\'applicazione',
'plugins': 'plugins',
'Plugins': 'I Plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Powered by',
'previous 100 rows': '100 righe precedenti',
'private files': 'private files',
'Private files': 'Private files',
'Query:': 'Richiesta (query):',
'record': 'record',
'record does not exist': 'il record non esiste',
'record id': 'ID del record',
'register': 'registrazione',
'Remove compiled': 'rimozione codice compilato',
'request': 'request',
'Resolve Conflict file': 'File di risoluzione conflitto',
'response': 'response',
'restore': 'ripristino',
'revert': 'versione precedente',
'Rows in table': 'Righe nella tabella',
'Rows selected': 'Righe selezionate',
'rules are not defined': 'rules are not defined',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'Running on %s',
'Save': 'Save',
'Save via Ajax': 'Save via Ajax',
'Saved file hash:': 'Hash del file salvato:',
'selected': 'selezionato',
'session': 'session',
'session expired': 'sessions scaduta',
'shell': 'shell',
'Site': 'sito',
'some files could not be removed': 'non è stato possibile rimuovere alcuni files',
'Start wizard': 'start wizard',
'state': 'stato',
'static': 'statico',
'Static files': 'Files statici',
'Stylesheet': 'Foglio di stile (stylesheet)',
'submit': 'invia',
'Submit': 'Submit',
'Sure you want to delete this object?': 'Vuoi veramente cancellare questo oggetto?',
'table': 'tabella',
'test': 'test',
'Testing application': 'Test applicazione in corsg',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La richiesta (query) è una condizione come ad esempio "db.tabella1.campo1==\'valore\'". Una condizione come "db.tabella1.campo1==db.tabella2.campo2" produce un "JOIN" SQL.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'the application logic, each URL path is mapped in one exposed function in the controller': 'logica dell\'applicazione, ogni percorso "URL" corrisponde ad una funzione esposta da un controller',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'the data representation, define database tables and sets': 'rappresentazione dei dati, definizione di tabelle di database e di "set" ',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'the presentations layer, views are also known as templates': 'Presentazione dell\'applicazione, viste (views, chiamate anche "templates")',
'There are no controllers': 'Non ci sono controller',
'There are no models': 'Non ci sono modelli',
'There are no modules': 'Non ci sono moduli',
'There are no plugins': 'There are no plugins',
'There are no static files': 'Non ci sono file statici',
'There are no translators, only default language is supported': 'Non ci sono traduzioni, viene solo supportato il linguaggio di base',
'There are no views': 'Non ci sono viste ("view")',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'these files are served without processing, your images go here': 'questi files vengono serviti così come sono, le immagini vanno qui',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'This is the %(filename)s template': 'Questo è il template %(filename)s',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'TM': 'TM',
'to previous version.': 'torna a versione precedente',
'To create a plugin, name a file/folder plugin_[name]': 'Per creare un plugin, chiamare un file o cartella plugin_[nome]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'translation strings for the application': "stringhe di traduzioni per l'applicazione",
'Translation strings for the application': 'Translation strings for the application',
'try': 'prova',
'try something like': 'prova qualcosa come',
'try view': 'try view',
'Unable to check for upgrades': 'Impossibile controllare presenza di aggiornamenti',
'unable to create application "%s"': 'impossibile creare applicazione "%s"',
'unable to delete file "%(filename)s"': 'impossibile rimuovere file "%(plugin)s"',
'unable to delete file plugin "%(plugin)s"': 'impossibile rimuovere file di plugin "%(plugin)s"',
'Unable to download app because:': 'Impossibile scaricare applicazione perché',
'Unable to download because': 'Impossibile scaricare perché',
'Unable to download because:': 'Unable to download because:',
'unable to parse csv file': 'non riesco a decodificare questo file CSV',
'unable to uninstall "%s"': 'impossibile disinstallare "%s"',
'unable to upgrade because "%s"': 'impossibile aggiornare perché "%s"',
'uncheck all': 'smarca tutti',
'Uninstall': 'disinstalla',
'update': 'aggiorna',
'update all languages': 'aggiorna tutti i linguaggi',
'Update:': 'Aggiorna:',
'upgrade web2py now': 'upgrade web2py now',
'upload': 'upload',
'Upload & install packed application': 'Carica ed installa pacchetto con applicazione',
'Upload a package:': 'Upload a package:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'carica applicazione:',
'upload file:': 'carica file:',
'upload plugin file:': 'carica file di plugin:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Per costruire richieste (query) più complesse si usano (...)&(...) come "e" (AND), (...)|(...) come "o" (OR), e ~(...) come negazione (NOT).',
'Use an url:': 'Use an url:',
'variables': 'variables',
'Version': 'Versione',
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
'versioning': 'sistema di versioni',
'Versioning': 'Versioning',
'view': 'vista',
'View': 'Vista',
'Views': 'viste',
'views': 'viste',
'web2py Recent Tweets': 'Tweets recenti per web2py',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py è aggiornato',
'web2py Recent Tweets': 'Tweets recenti per web2py',
'web2py upgraded; please restart it': 'web2py aggiornato; prego riavviarlo',
'Welcome %s': 'Benvenuto %s',
'Welcome to web2py': 'Benvenuto su web2py',
'YES': 'SI',
}
+17 -3
View File
@@ -123,6 +123,21 @@ if session.authorized:
else:
session.last_time = t0
if request.vars.is_mobile in ('true','false','auto'):
session.is_mobile = request.vars.is_mobile or 'auto'
if request.controller=='default' and request.function=='index':
if not request.vars.is_mobile:
session.is_mobile = 'auto'
if not session.is_mobile:
session.is_mobile = 'auto'
if session.is_mobile == 'true':
is_mobile = True
elif session.is_mobile == 'false':
is_mobile = False
else:
is_mobile = request.user_agent().is_mobile
if request.controller == "webservices":
basic = request.env.http_authorization
if not basic or not basic[:6].lower() == 'basic ':
@@ -132,8 +147,8 @@ if request.controller == "webservices":
time.sleep(10)
raise HTTP(403,"Not authorized")
elif not session.authorized and not \
(request.controller == 'default' and \
request.function in ('index','user')):
(request.controller+'/'+request.function in
('default/index','default/user','plugin_jqmobile/index','plugin_jqmobile/about')):
if request.env.query_string:
query_string = '?' + request.env.query_string
@@ -150,7 +165,6 @@ elif session.authorized and \
request.function == 'index':
redirect(URL(request.application, 'default', 'site'))
if request.controller=='appadmin' and DEMO_MODE:
session.flash = 'Appadmin disabled in demo mode'
redirect(URL('default','sites'))
+1 -1
View File
@@ -8,7 +8,7 @@ def A_button(*a,**b):
return A(*a,**b)
def button(href, label):
if request.user_agent().is_mobile:
if is_mobile:
ret = A_button(SPAN(label), _href=href)
else:
ret = A(SPAN(label),_class='button',_href=href)
File diff suppressed because one or more lines are too long
+10 -8
View File
@@ -10,7 +10,7 @@ h3 {font-size:2.00em}
h4 {font-size:1.50em}
h5 {font-size:1.25em}
h6 {font-size:1.12em}
th,label {font-weight:bold; white-space:nowrap}
th,label {font-weight:bold; white-space:nowrap;}
td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
@@ -69,7 +69,6 @@ fieldset {padding:16px; border-top:1px #DEDEDE solid}
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
/* fix ie problem with menu */
.ie-lte7 .topbar .container {z-index:2}
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
@@ -187,10 +186,7 @@ div.error {
.web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {
padding:2px 5px 2px 5px;
vertical-align:middle;
}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
@@ -299,10 +295,16 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; }
.ie9 #w2p_query_panel {padding-bottom:2px}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
}
/* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
File diff suppressed because one or more lines are too long
+49 -14
View File
@@ -39,7 +39,7 @@ function web2py_ajax_init(target) {
function web2py_event_handlers() {
var doc = jQuery(document)
doc.on('click', '.flash', function(e){jQuery(this).fadeOut('slow'); e.preventDefault();});
doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();});
doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();});
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
jQuery(function() {
var flash = jQuery('.flash');
flash.hide();
if(flash.html()) flash.slideDown();
if(flash.html()) flash.append('<span style="float:right;">&times;</span>').slideDown();
web2py_ajax_init(document);
web2py_event_handlers();
});
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
form.submit(function(e){
jQuery('.flash').hide().html('');
web2py_ajax_page('post',action,form.serialize(),target);
e.preventDefault();
e.preventDefault();
});
});
}
function web2py_trap_link(target) {
jQuery('#'+target+' a.w2p_trap').each(function(i){
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
}
function web2py_ajax_page(method, action, data, target) {
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
web2py_trap_link(target);
web2py_ajax_init('#'+target);
if(command)
eval(decodeURIComponent(command));
eval(decodeURIComponent(command));
if(flash)
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
}
});
}
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
}
} else {
// run once (no timeout specified)
element.reload_counter = Infinity;
element.reload_counter = Infinity;
web2py_ajax_page('get', action, null, target);
} }); }
function web2py_comet(url,onmessage,onopen,onclose) {
function web2py_websocket(url,onmessage,onopen,onclose) {
if ("WebSocket" in window) {
var ws = new WebSocket(url);
ws.onopen = onopen?onopen:(function(){});
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
} else return false; // not supported
}
function web2py_calc_entropy(mystring) {
//calculate a simple entropy for a given string
var csets = new Array(
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
'0123456789abcdefghijklmnopqrstuvwxyz');
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) { // classify this character
var c = mystringlist[i], inset=5;
for(var j = 0; j<csets.length; j++)
if (csets[j].indexOf(c) != -1) {inset = j; break;}
//calculate effect of character on alphabet size
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
else if (!(c in other)) {score += 1;other[c] = 1;}
if (inset != lastset) {score += 1;lastset = inset;}
}
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
return Math.round(entropy*100)/100
}
function web2py_validate_entropy(myfield, req_entropy) {
var validator = function () {
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
if(v<=0.5) {r=1.0; g=2.0*v;}
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
var color = '#'+rs(r)+rs(g)+rs(b);
myfield.css('background-color',color);
entropy_callback = myfield.data('entropy_callback');
if(entropy_callback) entropy_callback(v);
}
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}
File diff suppressed because one or more lines are too long
@@ -1,173 +0,0 @@
/*! jQuery Mobile v1.0 jquerymobile.com | jquery.org/license */
(function(a,e){if(a.cleanData){var b=a.cleanData;a.cleanData=function(f){for(var c=0,h;(h=f[c])!=null;c++)a(h).triggerHandler("remove");b(f)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){a(this).triggerHandler("remove")});return d.call(a(this),b,c)})}}a.widget=function(b,c,h){var d=b.split(".")[0],e,b=b.split(".")[1];e=d+"-"+b;if(!h)h=c,c=a.Widget;a.expr[":"][e]=function(c){return!!a.data(c,
b)};a[d]=a[d]||{};a[d][b]=function(a,b){arguments.length&&this._createWidget(a,b)};c=new c;c.options=a.extend(true,{},c.options);a[d][b].prototype=a.extend(true,c,{namespace:d,widgetName:b,widgetEventPrefix:a[d][b].prototype.widgetEventPrefix||b,widgetBaseClass:e},h);a.widget.bridge(b,a[d][b])};a.widget.bridge=function(b,c){a.fn[b]=function(d){var g=typeof d==="string",i=Array.prototype.slice.call(arguments,1),k=this,d=!g&&i.length?a.extend.apply(null,[true,d].concat(i)):d;if(g&&d.charAt(0)==="_")return k;
g?this.each(function(){var c=a.data(this,b);if(!c)throw"cannot call methods on "+b+" prior to initialization; attempted to call method '"+d+"'";if(!a.isFunction(c[d]))throw"no such method '"+d+"' for "+b+" widget instance";var g=c[d].apply(c,i);if(g!==c&&g!==e)return k=g,false}):this.each(function(){var e=a.data(this,b);e?e.option(d||{})._init():a.data(this,b,new c(d,this))});return k}};a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)};a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",
options:{disabled:false},_createWidget:function(b,c){a.data(c,this.widgetName,this);this.element=a(c);this.options=a.extend(true,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){var b={};a.metadata&&(b=a.metadata.get(element)[this.widgetName]);return b},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);
this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(b,c){var d=b;if(arguments.length===0)return a.extend({},this.options);if(typeof b==="string"){if(c===e)return this.options[b];d={};d[b]=c}this._setOptions(d);return this},_setOptions:function(b){var c=this;a.each(b,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b;a==="disabled"&&
this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(b,c,d){var e=this.options[b],c=a.Event(c);c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase();d=d||{};if(c.originalEvent)for(var b=a.event.props.length,i;b;)i=a.event.props[--b],c[i]=c.originalEvent[i];this.element.trigger(c,
d);return!(a.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
(function(a,e){a.widget("mobile.widget",{_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments);this._trigger("init")},_getCreateOptions:function(){var b=this.element,d={};a.each(this.options,function(a){var c=b.jqmData(a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()}));c!==e&&(d[a]=c)});return d},enhanceWithin:function(b){var d=a(b).closest(":jqmData(role='page')").data("page"),d=d&&d.keepNativeSelector()||"";a(this.options.initSelector,b).not(d)[this.widgetName]()}})})(jQuery);
(function(a){a(window);var e=a("html");a.mobile.media=function(){var b={},d=a("<div id='jquery-mediatest'>"),f=a("<body>").append(d);return function(a){if(!(a in b)){var h=document.createElement("style"),g="@media "+a+" { #jquery-mediatest { position:absolute; } }";h.type="text/css";h.styleSheet?h.styleSheet.cssText=g:h.appendChild(document.createTextNode(g));e.prepend(f).prepend(h);b[a]=d.css("position")==="absolute";f.add(h).remove()}return b[a]}}()})(jQuery);
(function(a,e){function b(a){var b=a.charAt(0).toUpperCase()+a.substr(1),a=(a+" "+c.join(b+" ")+b).split(" "),d;for(d in a)if(f[a[d]]!==e)return true}var d=a("<body>").prependTo("html"),f=d[0].style,c=["Webkit","Moz","O"],h="palmGetResource"in window,g=window.operamini&&{}.toString.call(window.operamini)==="[object OperaMini]",i=window.blackberry;a.mobile.browser={};a.mobile.browser.ie=function(){for(var a=3,b=document.createElement("div"),c=b.all||[];b.innerHTML="<\!--[if gt IE "+ ++a+"]><br><![endif]--\>",
c[0];);return a>4?a:!a}();a.extend(a.support,{orientation:"orientation"in window&&"onorientationchange"in window,touch:"ontouchend"in document,cssTransitions:"WebKitTransitionEvent"in window,pushState:"pushState"in history&&"replaceState"in history,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!b("content"),touchOverflow:!!b("overflowScrolling"),boxShadow:!!b("boxShadow")&&!i,scrollTop:("pageXOffset"in window||"scrollTop"in document.documentElement||"scrollTop"in d[0])&&!h&&!g,dynamicBaseTag:function(){var b=
location.protocol+"//"+location.host+location.pathname+"ui-dir/",c=a("head base"),f=null,e="",h;c.length?e=c.attr("href"):c=f=a("<base>",{href:b}).appendTo("head");h=a("<a href='testurl' />").prependTo(d)[0].href;c[0].href=e||location.pathname;f&&f.remove();return h.indexOf(b)===0}()});d.remove();h=function(){var a=window.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")>-1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}();
a.mobile.ajaxBlacklist=window.blackberry&&!window.WebKitPoint||g||h;h&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")});a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")})(jQuery);
(function(a,e,b,d){function f(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function c(b){for(var c={},f,d;b;){f=a.data(b,n);for(d in f)if(f[d])c[d]=c.hasVirtualBinding=true;b=b.parentNode}return c}function h(){v&&(clearTimeout(v),v=0);v=setTimeout(function(){E=v=0;u.length=0;D=false;y=true},a.vmouse.resetTimerDuration)}function g(b,c,r){var e,h;if(!(h=r&&r[b])){if(r=!r)a:{for(r=c.target;r;){if((h=a.data(r,n))&&(!b||h[b]))break a;r=r.parentNode}r=null}h=r}if(h){e=c;var r=
e.type,j,g;e=a.Event(e);e.type=b;h=e.originalEvent;j=a.event.props;if(h)for(g=j.length;g;)b=j[--g],e[b]=h[b];if(r.search(/mouse(down|up)|click/)>-1&&!e.which)e.which=1;if(r.search(/^touch/)!==-1&&(b=f(h),r=b.touches,b=b.changedTouches,r=r&&r.length?r[0]:b&&b.length?b[0]:d))for(h=0,len=z.length;h<len;h++)b=z[h],e[b]=r[b];a(c.target).trigger(e)}return e}function i(b){var c=a.data(b.target,A);if(!D&&(!E||E!==c))if(c=g("v"+b.type,b))c.isDefaultPrevented()&&b.preventDefault(),c.isPropagationStopped()&&
b.stopPropagation(),c.isImmediatePropagationStopped()&&b.stopImmediatePropagation()}function k(b){var d=f(b).touches,e;if(d&&d.length===1&&(e=b.target,d=c(e),d.hasVirtualBinding))E=r++,a.data(e,A,E),v&&(clearTimeout(v),v=0),w=y=false,e=f(b).touches[0],x=e.pageX,t=e.pageY,g("vmouseover",b,d),g("vmousedown",b,d)}function l(a){y||(w||g("vmousecancel",a,c(a.target)),w=true,h())}function o(b){if(!y){var d=f(b).touches[0],r=w,e=a.vmouse.moveDistanceThreshold;w=w||Math.abs(d.pageX-x)>e||Math.abs(d.pageY-
t)>e;flags=c(b.target);w&&!r&&g("vmousecancel",b,flags);g("vmousemove",b,flags);h()}}function m(a){if(!y){y=true;var b=c(a.target),d;g("vmouseup",a,b);if(!w&&(d=g("vclick",a,b))&&d.isDefaultPrevented())d=f(a).changedTouches[0],u.push({touchID:E,x:d.clientX,y:d.clientY}),D=true;g("vmouseout",a,b);w=false;h()}}function p(b){var b=a.data(b,n),c;if(b)for(c in b)if(b[c])return true;return false}function j(){}function q(b){var c=b.substr(1);return{setup:function(){p(this)||a.data(this,n,{});a.data(this,
n)[b]=true;s[b]=(s[b]||0)+1;s[b]===1&&B.bind(c,i);a(this).bind(c,j);if(C)s.touchstart=(s.touchstart||0)+1,s.touchstart===1&&B.bind("touchstart",k).bind("touchend",m).bind("touchmove",o).bind("scroll",l)},teardown:function(){--s[b];s[b]||B.unbind(c,i);C&&(--s.touchstart,s.touchstart||B.unbind("touchstart",k).unbind("touchmove",o).unbind("touchend",m).unbind("scroll",l));var d=a(this),f=a.data(this,n);f&&(f[b]=false);d.unbind(c,j);p(this)||d.removeData(n)}}}var n="virtualMouseBindings",A="virtualTouchID",
e="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),z="clientX clientY pageX pageY screenX screenY".split(" "),s={},v=0,x=0,t=0,w=false,u=[],D=false,y=false,C="addEventListener"in b,B=a(b),r=1,E=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10,resetTimerDuration:1500};for(var F=0;F<e.length;F++)a.event.special[e[F]]=q(e[F]);C&&b.addEventListener("click",function(b){var c=u.length,d=b.target,f,r,e,h,j;if(c){f=b.clientX;r=b.clientY;threshold=a.vmouse.clickDistanceThreshold;
for(e=d;e;){for(h=0;h<c;h++)if(j=u[h],e===d&&Math.abs(j.x-f)<threshold&&Math.abs(j.y-r)<threshold||a.data(e,A)===j.touchID){b.preventDefault();b.stopPropagation();return}e=e.parentNode}}},true)})(jQuery,window,document);
(function(a,e,b){function d(b,c,d){var f=d.type;d.type=c;a.event.handle.call(b,d);d.type=f}a.each("touchstart touchmove touchend orientationchange throttledresize tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(b,c){a.fn[c]=function(a){return a?this.bind(c,a):this.trigger(c)};a.attrFn[c]=true});var f=a.support.touch,c=f?"touchstart":"mousedown",h=f?"touchend":"mouseup",g=f?"touchmove":"mousemove";a.event.special.scrollstart={enabled:true,setup:function(){function b(a,
e){f=e;d(c,f?"scrollstart":"scrollstop",a)}var c=this,f,e;a(c).bind("touchmove scroll",function(c){a.event.special.scrollstart.enabled&&(f||b(c,true),clearTimeout(e),e=setTimeout(function(){b(c,false)},50))})}};a.event.special.tap={setup:function(){var b=this,c=a(b);c.bind("vmousedown",function(f){function e(){clearTimeout(q)}function h(){e();c.unbind("vclick",g).unbind("vmouseup",e).unbind("vmousecancel",h)}function g(a){h();j==a.target&&d(b,"tap",a)}if(f.which&&f.which!==1)return false;var j=f.target,
q;c.bind("vmousecancel",h).bind("vmouseup",e).bind("vclick",g);q=setTimeout(function(){d(b,"taphold",a.Event("taphold"))},750)})}};a.event.special.swipe={scrollSupressionThreshold:10,durationThreshold:1E3,horizontalDistanceThreshold:30,verticalDistanceThreshold:75,setup:function(){var d=a(this);d.bind(c,function(c){function f(b){if(m){var c=b.originalEvent.touches?b.originalEvent.touches[0]:b;p={time:(new Date).getTime(),coords:[c.pageX,c.pageY]};Math.abs(m.coords[0]-p.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&
b.preventDefault()}}var e=c.originalEvent.touches?c.originalEvent.touches[0]:c,m={time:(new Date).getTime(),coords:[e.pageX,e.pageY],origin:a(c.target)},p;d.bind(g,f).one(h,function(){d.unbind(g,f);m&&p&&p.time-m.time<a.event.special.swipe.durationThreshold&&Math.abs(m.coords[0]-p.coords[0])>a.event.special.swipe.horizontalDistanceThreshold&&Math.abs(m.coords[1]-p.coords[1])<a.event.special.swipe.verticalDistanceThreshold&&m.origin.trigger("swipe").trigger(m.coords[0]>p.coords[0]?"swipeleft":"swiperight");
m=p=b})})}};(function(a,b){function c(){var a=f();a!==e&&(e=a,d.trigger("orientationchange"))}var d=a(b),f,e;a.event.special.orientationchange={setup:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;e=f();d.bind("throttledresize",c)},teardown:function(){if(a.support.orientation&&a.mobile.orientationChangeEnabled)return false;d.unbind("throttledresize",c)},add:function(a){var b=a.handler;a.handler=function(a){a.orientation=f();return b.apply(this,arguments)}}};a.event.special.orientationchange.orientation=
f=function(){var c=true,c=document.documentElement;return(c=a.support.orientation?b.orientation%180==0:c&&c.clientWidth/c.clientHeight<1.1)?"portrait":"landscape"}})(jQuery,e);(function(){a.event.special.throttledresize={setup:function(){a(this).bind("resize",b)},teardown:function(){a(this).unbind("resize",b)}};var b=function(){f=(new Date).getTime();e=f-c;e>=250?(c=f,a(this).trigger("throttledresize")):(d&&clearTimeout(d),d=setTimeout(b,250-e))},c=0,d,f,e})();a.each({scrollstop:"scrollstart",taphold:"tap",
swipeleft:"swipe",swiperight:"swipe"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)}}})})(jQuery,this);
(function(a,e,b){function d(a){a=a||location.href;return"#"+a.replace(/^[^#]*#?(.*)$/,"$1")}var f="hashchange",c=document,h,g=a.event.special,i=c.documentMode,k="on"+f in e&&(i===b||i>7);a.fn[f]=function(a){return a?this.bind(f,a):this.trigger(f)};a.fn[f].delay=50;g[f]=a.extend(g[f],{setup:function(){if(k)return false;a(h.start)},teardown:function(){if(k)return false;a(h.stop)}});h=function(){function h(){var b=d(),c=n(p);if(b!==p)q(p=b,c),a(e).trigger(f);else if(c!==p)location.href=location.href.replace(/#.*/,
"")+c;i=setTimeout(h,a.fn[f].delay)}var g={},i,p=d(),j=function(a){return a},q=j,n=j;g.start=function(){i||h()};g.stop=function(){i&&clearTimeout(i);i=b};a.browser.msie&&!k&&function(){var b,e;g.start=function(){if(!b)e=(e=a.fn[f].src)&&e+d(),b=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){e||q(d());h()}).attr("src",e||"javascript:0").insertAfter("body")[0].contentWindow,c.onpropertychange=function(){try{if(event.propertyName==="title")b.document.title=c.title}catch(a){}}};
g.stop=j;n=function(){return d(b.location.href)};q=function(d,e){var h=b.document,g=a.fn[f].domain;if(d!==e)h.title=c.title,h.open(),g&&h.write('<script>document.domain="'+g+'"<\/script>'),h.close(),b.location.hash=d}}();return g}()})(jQuery,this);
(function(a){a.widget("mobile.page",a.mobile.widget,{options:{theme:"c",domCache:false,keepNativeDefault:":jqmData(role='none'), :jqmData(role='nojs')"},_create:function(){this._trigger("beforecreate");this.element.attr("tabindex","0").addClass("ui-page ui-body-"+this.options.theme)},keepNativeSelector:function(){var e=this.options;return e.keepNative&&a.trim(e.keepNative)&&e.keepNative!==e.keepNativeDefault?[e.keepNative,e.keepNativeDefault].join(", "):e.keepNativeDefault}})})(jQuery);
(function(a,e){var b={};a.extend(a.mobile,{ns:"",subPageUrlKey:"ui-page",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",ajaxEnabled:true,hashListeningEnabled:true,linkBindingEnabled:true,defaultPageTransition:"slide",minScrollBack:250,defaultDialogTransition:"pop",loadingMessage:"loading",pageLoadErrorMessage:"Error Loading Page",autoInitializePage:true,pushStateEnabled:true,orientationChangeEnabled:true,gradeA:function(){return a.support.mediaquery||a.mobile.browser.ie&&a.mobile.browser.ie>=
7},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},silentScroll:function(b){if(a.type(b)!=="number")b=a.mobile.defaultHomeScroll;a.event.special.scrollstart.enabled=false;
setTimeout(function(){e.scrollTo(0,b);a(document).trigger("silentscroll",{x:0,y:b})},20);setTimeout(function(){a.event.special.scrollstart.enabled=true},150)},nsNormalizeDict:b,nsNormalize:function(c){return!c?void 0:b[c]||(b[c]=a.camelCase(a.mobile.ns+c))},getInheritedTheme:function(a,b){for(var d=a[0],f="",e=/ui-(bar|body)-([a-z])\b/,l,o;d;){l=d.className||"";if((o=e.exec(l))&&(f=o[2]))break;d=d.parentNode}return f||b||"a"}});a.fn.jqmData=function(b,d){var f;typeof b!="undefined"&&(f=this.data(b?
a.mobile.nsNormalize(b):b,d));return f};a.jqmData=function(b,d,f){var e;typeof d!="undefined"&&(e=a.data(b,d?a.mobile.nsNormalize(d):d,f));return e};a.fn.jqmRemoveData=function(b){return this.removeData(a.mobile.nsNormalize(b))};a.jqmRemoveData=function(b,d){return a.removeData(b,a.mobile.nsNormalize(d))};a.fn.removeWithDependents=function(){a.removeWithDependents(this)};a.removeWithDependents=function(b){b=a(b);(b.jqmData("dependents")||a()).remove();b.remove()};a.fn.addDependents=function(b){a.addDependents(a(this),
b)};a.addDependents=function(b,d){var f=a(b).jqmData("dependents")||a();a(b).jqmData("dependents",a.merge(f,d))};a.fn.getEncodedText=function(){return a("<div/>").text(a(this).text()).html()};var d=a.find,f=/:jqmData\(([^)]*)\)/g;a.find=function(b,e,g,i){b=b.replace(f,"[data-"+(a.mobile.ns||"")+"$1]");return d.call(this,b,e,g,i)};a.extend(a.find,d);a.find.matches=function(b,d){return a.find(b,null,null,d)};a.find.matchesSelector=function(b,d){return a.find(d,null,null,[b]).length>0}})(jQuery,this);
(function(a,e){function b(a){var b=a.find(".ui-title:eq(0)");b.length?b.focus():a.focus()}function d(b){q&&(!q.closest(".ui-page-active").length||b)&&q.removeClass(a.mobile.activeBtnClass);q=null}function f(){z=false;A.length>0&&a.mobile.changePage.apply(null,A.pop())}function c(c,d,f,e){var g=a.mobile.urlHistory.getActive(),j=a.support.touchOverflow&&a.mobile.touchOverflowEnabled,i=g.lastScroll||(j?0:a.mobile.defaultHomeScroll),g=h();window.scrollTo(0,a.mobile.defaultHomeScroll);d&&d.data("page")._trigger("beforehide",
null,{nextPage:c});j||c.height(g+i);c.data("page")._trigger("beforeshow",null,{prevPage:d||a("")});a.mobile.hidePageLoadingMsg();j&&i&&(c.addClass("ui-mobile-pre-transition"),b(c),c.is(".ui-native-fixed")?c.find(".ui-content").scrollTop(i):c.scrollTop(i));f=(a.mobile.transitionHandlers[f||"none"]||a.mobile.defaultTransitionHandler)(f,e,c,d);f.done(function(){j||(c.height(""),b(c));j||a.mobile.silentScroll(i);d&&(j||d.height(""),d.data("page")._trigger("hide",null,{nextPage:c}));c.data("page")._trigger("show",
null,{prevPage:d||a("")})});return f}function h(){var b=a.event.special.orientationchange.orientation()==="portrait",c=b?screen.availHeight:screen.availWidth,b=Math.max(b?480:320,a(window).height());return Math.min(c,b)}function g(){(!a.support.touchOverflow||!a.mobile.touchOverflowEnabled)&&a("."+a.mobile.activePageClass).css("min-height",h())}function i(b,c){c&&b.attr("data-"+a.mobile.ns+"role",c);b.page()}function k(a){for(;a;){if(typeof a.nodeName==="string"&&a.nodeName.toLowerCase()=="a")break;
a=a.parentNode}return a}function l(b){var b=a(b).closest(".ui-page").jqmData("url"),c=t.hrefNoHash;if(!b||!j.isPath(b))b=c;return j.makeUrlAbsolute(b,c)}var o=a(window),m=a("html"),p=a("head"),j={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,parseUrl:function(b){if(a.type(b)==="object")return b;b=j.urlParseRE.exec(b||"")||[];return{href:b[0]||"",hrefNoHash:b[1]||
"",hrefNoSearch:b[2]||"",domain:b[3]||"",protocol:b[4]||"",doubleSlash:b[5]||"",authority:b[6]||"",username:b[8]||"",password:b[9]||"",host:b[10]||"",hostname:b[11]||"",port:b[12]||"",pathname:b[13]||"",directory:b[14]||"",filename:b[15]||"",search:b[16]||"",hash:b[17]||""}},makePathAbsolute:function(a,b){if(a&&a.charAt(0)==="/")return a;for(var a=a||"",c=(b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"")?b.split("/"):[],d=a.split("/"),f=0;f<d.length;f++){var e=d[f];switch(e){case ".":break;case "..":c.length&&
c.pop();break;default:c.push(e)}}return"/"+c.join("/")},isSameDomain:function(a,b){return j.parseUrl(a).domain===j.parseUrl(b).domain},isRelativeUrl:function(a){return j.parseUrl(a).protocol===""},isAbsoluteUrl:function(a){return j.parseUrl(a).protocol!==""},makeUrlAbsolute:function(a,b){if(!j.isRelativeUrl(a))return a;var c=j.parseUrl(a),d=j.parseUrl(b),f=c.protocol||d.protocol,e=c.protocol?c.doubleSlash:c.doubleSlash||d.doubleSlash,h=c.authority||d.authority,g=c.pathname!=="",i=j.makePathAbsolute(c.pathname||
d.filename,d.pathname);return f+e+h+i+(c.search||!g&&d.search||"")+c.hash},addSearchParams:function(b,c){var d=j.parseUrl(b),f=typeof c==="object"?a.param(c):c,e=d.search||"?";return d.hrefNoSearch+e+(e.charAt(e.length-1)!=="?"?"&":"")+f+(d.hash||"")},convertUrlToDataUrl:function(a){var b=j.parseUrl(a);if(j.isEmbeddedPage(b))return b.hash.split(s)[0].replace(/^#/,"");else if(j.isSameDomain(b,t))return b.hrefNoHash.replace(t.domain,"");return a},get:function(a){if(a===e)a=location.hash;return j.stripHash(a).replace(/[^\/]*\.[^\/*]+$/,
"")},getFilePath:function(b){var c="&"+a.mobile.subPageUrlKey;return b&&b.split(c)[0].split(s)[0]},set:function(a){location.hash=a},isPath:function(a){return/\//.test(a)},clean:function(a){return a.replace(t.domain,"")},stripHash:function(a){return a.replace(/^#/,"")},cleanHash:function(a){return j.stripHash(a.replace(/\?.*$/,"").replace(s,""))},isExternal:function(a){a=j.parseUrl(a);return a.protocol&&a.domain!==x.domain?true:false},hasProtocol:function(a){return/^(:?\w+:)/.test(a)},isFirstPageUrl:function(b){var b=
j.parseUrl(j.makeUrlAbsolute(b,t)),c=a.mobile.firstPage,c=c&&c[0]?c[0].id:e;return(b.hrefNoHash===x.hrefNoHash||w&&b.hrefNoHash===t.hrefNoHash)&&(!b.hash||b.hash==="#"||c&&b.hash.replace(/^#/,"")===c)},isEmbeddedPage:function(a){a=j.parseUrl(a);return a.protocol!==""?a.hash&&(a.hrefNoHash===x.hrefNoHash||w&&a.hrefNoHash===t.hrefNoHash):/^#/.test(a.href)}},q=null,n={stack:[],activeIndex:0,getActive:function(){return n.stack[n.activeIndex]},getPrev:function(){return n.stack[n.activeIndex-1]},getNext:function(){return n.stack[n.activeIndex+
1]},addNew:function(a,b,c,d,f){n.getNext()&&n.clearForward();n.stack.push({url:a,transition:b,title:c,pageUrl:d,role:f});n.activeIndex=n.stack.length-1},clearForward:function(){n.stack=n.stack.slice(0,n.activeIndex+1)},directHashChange:function(b){var c,d,f;this.getActive();a.each(n.stack,function(a,e){b.currentUrl===e.url&&(c=a<n.activeIndex,d=!c,f=a)});this.activeIndex=f!==e?f:this.activeIndex;c?(b.either||b.isBack)(true):d&&(b.either||b.isForward)(false)},ignoreNextHashChange:false},A=[],z=false,
s="&ui-state=dialog",v=p.children("base"),x=j.parseUrl(location.href),t=v.length?j.parseUrl(j.makeUrlAbsolute(v.attr("href"),x.href)):x,w=x.hrefNoHash!==t.hrefNoHash,u=a.support.dynamicBaseTag?{element:v.length?v:a("<base>",{href:t.hrefNoHash}).prependTo(p),set:function(a){u.element.attr("href",j.makeUrlAbsolute(a,t))},reset:function(){u.element.attr("href",t.hrefNoHash)}}:e,D=true,y,C,B;y=function(){var b=o;a.support.touchOverflow&&a.mobile.touchOverflowEnabled&&(b=a(".ui-page-active"),b=b.is(".ui-native-fixed")?
b.find(".ui-content"):b);return b};C=function(b){if(D){var c=a.mobile.urlHistory.getActive();if(c)b=b&&b.scrollTop(),c.lastScroll=b<a.mobile.minScrollBack?a.mobile.defaultHomeScroll:b}};B=function(){setTimeout(C,100,a(this))};o.bind(a.support.pushState?"popstate":"hashchange",function(){D=false});o.one(a.support.pushState?"popstate":"hashchange",function(){D=true});o.one("pagecontainercreate",function(){a.mobile.pageContainer.bind("pagechange",function(){var a=y();D=true;a.unbind("scrollstop",B);
a.bind("scrollstop",B)})});y().bind("scrollstop",B);a.mobile.getScreenHeight=h;a.fn.animationComplete=function(b){return a.support.cssTransitions?a(this).one("webkitAnimationEnd",b):(setTimeout(b,0),a(this))};a.mobile.path=j;a.mobile.base=u;a.mobile.urlHistory=n;a.mobile.dialogHashKey=s;a.mobile.noneTransitionHandler=function(b,c,d,f){f&&f.removeClass(a.mobile.activePageClass);d.addClass(a.mobile.activePageClass);return a.Deferred().resolve(b,c,d,f).promise()};a.mobile.defaultTransitionHandler=a.mobile.noneTransitionHandler;
a.mobile.transitionHandlers={none:a.mobile.defaultTransitionHandler};a.mobile.allowCrossDomainPages=false;a.mobile.getDocumentUrl=function(b){return b?a.extend({},x):x.href};a.mobile.getDocumentBase=function(b){return b?a.extend({},t):t.href};a.mobile._bindPageRemove=function(){var b=a(this);!b.data("page").options.domCache&&b.is(":jqmData(external-page='true')")&&b.bind("pagehide.remove",function(){var b=a(this),c=new a.Event("pageremove");b.trigger(c);c.isDefaultPrevented()||b.removeWithDependents()})};
a.mobile.loadPage=function(b,c){var d=a.Deferred(),f=a.extend({},a.mobile.loadPage.defaults,c),h=null,g=null,m=j.makeUrlAbsolute(b,a.mobile.activePage&&l(a.mobile.activePage)||t.hrefNoHash);if(f.data&&f.type==="get")m=j.addSearchParams(m,f.data),f.data=e;if(f.data&&f.type==="post")f.reloadPage=true;var s=j.getFilePath(m),p=j.convertUrlToDataUrl(m);f.pageContainer=f.pageContainer||a.mobile.pageContainer;h=f.pageContainer.children(":jqmData(url='"+p+"')");h.length===0&&p&&!j.isPath(p)&&(h=f.pageContainer.children("#"+
p).attr("data-"+a.mobile.ns+"url",p));if(h.length===0)if(a.mobile.firstPage&&j.isFirstPageUrl(s))a.mobile.firstPage.parent().length&&(h=a(a.mobile.firstPage));else if(j.isEmbeddedPage(s))return d.reject(m,c),d.promise();u&&u.reset();if(h.length){if(!f.reloadPage)return i(h,f.role),d.resolve(m,c,h),d.promise();g=h}var n=f.pageContainer,k=new a.Event("pagebeforeload"),q={url:b,absUrl:m,dataUrl:p,deferred:d,options:f};n.trigger(k,q);if(k.isDefaultPrevented())return d.promise();if(f.showLoadMsg)var v=
setTimeout(function(){a.mobile.showPageLoadingMsg()},f.loadMsgDelay);!a.mobile.allowCrossDomainPages&&!j.isSameDomain(x,m)?d.reject(m,c):a.ajax({url:s,type:f.type,data:f.data,dataType:"html",success:function(e,n,k){var o=a("<div></div>"),l=e.match(/<title[^>]*>([^<]*)/)&&RegExp.$1,t=RegExp("\\bdata-"+a.mobile.ns+"url=[\"']?([^\"'>]*)[\"']?");RegExp("(<[^>]+\\bdata-"+a.mobile.ns+"role=[\"']?page[\"']?[^>]*>)").test(e)&&RegExp.$1&&t.test(RegExp.$1)&&RegExp.$1&&(b=s=j.getFilePath(RegExp.$1));u&&u.set(s);
o.get(0).innerHTML=e;h=o.find(":jqmData(role='page'), :jqmData(role='dialog')").first();h.length||(h=a("<div data-"+a.mobile.ns+"role='page'>"+e.split(/<\/?body[^>]*>/gmi)[1]+"</div>"));l&&!h.jqmData("title")&&(~l.indexOf("&")&&(l=a("<div>"+l+"</div>").text()),h.jqmData("title",l));if(!a.support.dynamicBaseTag){var x=j.get(s);h.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function(){var b=a(this).is("[href]")?"href":a(this).is("[src]")?"src":"action",c=a(this).attr(b),
c=c.replace(location.protocol+"//"+location.host+location.pathname,"");/^(\w+:|#|\/)/.test(c)||a(this).attr(b,x+c)})}h.attr("data-"+a.mobile.ns+"url",j.convertUrlToDataUrl(s)).attr("data-"+a.mobile.ns+"external-page",true).appendTo(f.pageContainer);h.one("pagecreate",a.mobile._bindPageRemove);i(h,f.role);m.indexOf("&"+a.mobile.subPageUrlKey)>-1&&(h=f.pageContainer.children(":jqmData(url='"+p+"')"));f.showLoadMsg&&(clearTimeout(v),a.mobile.hidePageLoadingMsg());q.xhr=k;q.textStatus=n;q.page=h;f.pageContainer.trigger("pageload",
q);d.resolve(m,c,h,g)},error:function(b,e,h){u&&u.set(j.get());q.xhr=b;q.textStatus=e;q.errorThrown=h;b=new a.Event("pageloadfailed");f.pageContainer.trigger(b,q);b.isDefaultPrevented()||(f.showLoadMsg&&(clearTimeout(v),a.mobile.hidePageLoadingMsg(),a("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>"+a.mobile.pageLoadErrorMessage+"</h1></div>").css({display:"block",opacity:0.96,top:o.scrollTop()+100}).appendTo(f.pageContainer).delay(800).fadeOut(400,function(){a(this).remove()})),
d.reject(m,c))}});return d.promise()};a.mobile.loadPage.defaults={type:"get",data:e,reloadPage:false,role:e,showLoadMsg:false,pageContainer:e,loadMsgDelay:50};a.mobile.changePage=function(b,h){if(z)A.unshift(arguments);else{var g=a.extend({},a.mobile.changePage.defaults,h);g.pageContainer=g.pageContainer||a.mobile.pageContainer;g.fromPage=g.fromPage||a.mobile.activePage;var p=g.pageContainer,k=new a.Event("pagebeforechange"),q={toPage:b,options:g};p.trigger(k,q);if(!k.isDefaultPrevented())if(b=q.toPage,
z=true,typeof b=="string")a.mobile.loadPage(b,g).done(function(b,c,d,f){z=false;c.duplicateCachedPage=f;a.mobile.changePage(d,c)}).fail(function(){z=false;d(true);f();g.pageContainer.trigger("pagechangefailed",q)});else{if(b[0]===a.mobile.firstPage[0]&&!g.dataUrl)g.dataUrl=x.hrefNoHash;var k=g.fromPage,l=g.dataUrl&&j.convertUrlToDataUrl(g.dataUrl)||b.jqmData("url"),v=l;j.getFilePath(l);var o=n.getActive(),t=n.activeIndex===0,w=0,u=document.title,y=g.role==="dialog"||b.jqmData("role")==="dialog";if(k&&
k[0]===b[0]&&!g.allowSamePageTransition)z=false,p.trigger("pagechange",q);else{i(b,g.role);g.fromHashChange&&n.directHashChange({currentUrl:l,isBack:function(){w=-1},isForward:function(){w=1}});try{document.activeElement&&document.activeElement.nodeName.toLowerCase()!="body"?a(document.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(B){}y&&o&&(l=(o.url||"")+s);if(g.changeHash!==false&&l)n.ignoreNextHashChange=true,j.set(l);var C=!o?u:b.jqmData("title")||b.children(":jqmData(role='header')").find(".ui-title").getEncodedText();
C&&u==document.title&&(u=C);b.jqmData("title")||b.jqmData("title",u);g.transition=g.transition||(w&&!t?o.transition:e)||(y?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition);w||n.addNew(l,g.transition,u,v,g.role);document.title=n.getActive().title;a.mobile.activePage=b;g.reverse=g.reverse||w<0;c(b,k,g.transition,g.reverse).done(function(){d();g.duplicateCachedPage&&g.duplicateCachedPage.remove();m.removeClass("ui-mobile-rendering");f();p.trigger("pagechange",q)})}}}};a.mobile.changePage.defaults=
{transition:e,reverse:false,changeHash:true,fromHashChange:false,role:e,duplicateCachedPage:e,pageContainer:e,showLoadMsg:true,dataUrl:e,fromPage:e,allowSamePageTransition:false};a.mobile._registerInternalEvents=function(){a("form").live("submit",function(b){var c=a(this);if(a.mobile.ajaxEnabled&&!c.is(":jqmData(ajax='false')")){var d=c.attr("method"),f=c.attr("target"),e=c.attr("action");if(!e&&(e=l(c),e===t.hrefNoHash))e=x.hrefNoSearch;e=j.makeUrlAbsolute(e,l(c));!j.isExternal(e)&&!f&&(a.mobile.changePage(e,
{type:d&&d.length&&d.toLowerCase()||"get",data:c.serialize(),transition:c.jqmData("transition"),direction:c.jqmData("direction"),reloadPage:true}),b.preventDefault())}});a(document).bind("vclick",function(b){if(!(b.which>1)&&a.mobile.linkBindingEnabled&&(b=k(b.target))&&j.parseUrl(b.getAttribute("href")||"#").hash!=="#")d(true),q=a(b).closest(".ui-btn").not(".ui-disabled"),q.addClass(a.mobile.activeBtnClass),a("."+a.mobile.activePageClass+" .ui-btn").not(b).blur()});a(document).bind("click",function(b){if(a.mobile.linkBindingEnabled){var c=
k(b.target);if(c&&!(b.which>1)){var f=a(c),h=function(){window.setTimeout(function(){d(true)},200)};if(f.is(":jqmData(rel='back')"))return window.history.back(),false;var g=l(f),c=j.makeUrlAbsolute(f.attr("href")||"#",g);if(!a.mobile.ajaxEnabled&&!j.isEmbeddedPage(c))h();else{if(c.search("#")!=-1)if(c=c.replace(/[^#]*#/,""))c=j.isPath(c)?j.makeUrlAbsolute(c,g):j.makeUrlAbsolute("#"+c,x.hrefNoHash);else{b.preventDefault();return}var g=f.is("[rel='external']")||f.is(":jqmData(ajax='false')")||f.is("[target]"),
i=a.mobile.allowCrossDomainPages&&x.protocol==="file:"&&c.search(/^https?:/)!=-1;g||j.isExternal(c)&&!i?h():(h=f.jqmData("transition"),g=(g=f.jqmData("direction"))&&g==="reverse"||f.jqmData("back"),f=f.attr("data-"+a.mobile.ns+"rel")||e,a.mobile.changePage(c,{transition:h,reverse:g,role:f}),b.preventDefault())}}}});a(".ui-page").live("pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=a(this),f=c.attr("href");f&&a.inArray(f,b)===-1&&(b.push(f),a.mobile.loadPage(f,
{role:c.attr("data-"+a.mobile.ns+"rel")}))})});a.mobile._handleHashChange=function(b){var c=j.stripHash(b),f={transition:a.mobile.urlHistory.stack.length===0?"none":e,changeHash:false,fromHashChange:true};if(!a.mobile.hashListeningEnabled||n.ignoreNextHashChange)n.ignoreNextHashChange=false;else{if(n.stack.length>1&&c.indexOf(s)>-1)if(a.mobile.activePage.is(".ui-dialog"))n.directHashChange({currentUrl:c,either:function(b){var d=a.mobile.urlHistory.getActive();c=d.pageUrl;a.extend(f,{role:d.role,transition:d.transition,
reverse:b})}});else{n.directHashChange({currentUrl:c,isBack:function(){window.history.back()},isForward:function(){window.history.forward()}});return}c?(c=typeof c==="string"&&!j.isPath(c)?j.makeUrlAbsolute("#"+c,t):c,a.mobile.changePage(c,f)):a.mobile.changePage(a.mobile.firstPage,f)}};o.bind("hashchange",function(){a.mobile._handleHashChange(location.hash)});a(document).bind("pageshow",g);a(window).bind("throttledresize",g)}})(jQuery);
(function(a,e){var b={},d=a(e),f=a.mobile.path.parseUrl(location.href);a.extend(b,{initialFilePath:f.pathname+f.search,initialHref:f.hrefNoHash,hashchangeFired:false,state:function(){return{hash:location.hash||"#"+b.initialFilePath,title:document.title,initialHref:b.initialHref}},resetUIKeys:function(b){var f="&"+a.mobile.subPageUrlKey,d=b.indexOf(a.mobile.dialogHashKey);d>-1?b=b.slice(0,d)+"#"+b.slice(d):b.indexOf(f)>-1&&(b=b.split(f).join("#"+f));return b},nextHashChangePrevented:function(c){a.mobile.urlHistory.ignoreNextHashChange=
c;b.onHashChangeDisabled=c},onHashChange:function(){if(!b.onHashChangeDisabled){var c,f;c=location.hash;var d=a.mobile.path.isPath(c),e=d?location.href:a.mobile.getDocumentUrl();c=d?c.replace("#",""):c;f=b.state();c=a.mobile.path.makeUrlAbsolute(c,e);d&&(c=b.resetUIKeys(c));history.replaceState(f,document.title,c)}},onPopState:function(c){var f=c.originalEvent.state;f&&(b.nextHashChangePrevented(true),setTimeout(function(){b.nextHashChangePrevented(false);a.mobile._handleHashChange(f.hash)},100))},
init:function(){d.bind("hashchange",b.onHashChange);d.bind("popstate",b.onPopState);location.hash===""&&history.replaceState(b.state(),document.title,location.href)}});a(function(){a.mobile.pushStateEnabled&&a.support.pushState&&b.init()})})(jQuery,this);
(function(a){function e(b,d,f,c){var e=new a.Deferred,g=d?" reverse":"",i="ui-mobile-viewport-transitioning viewport-"+b;f.animationComplete(function(){f.add(c).removeClass("out in reverse "+b);c&&c[0]!==f[0]&&c.removeClass(a.mobile.activePageClass);f.parent().removeClass(i);e.resolve(b,d,f,c)});f.parent().addClass(i);c&&c.addClass(b+" out"+g);f.addClass(a.mobile.activePageClass+" "+b+" in"+g);return e.promise()}a.mobile.css3TransitionHandler=e;if(a.mobile.defaultTransitionHandler===a.mobile.noneTransitionHandler)a.mobile.defaultTransitionHandler=
e})(jQuery,this);
(function(a){a.mobile.page.prototype.options.degradeInputs={color:false,date:false,datetime:false,"datetime-local":false,email:false,month:false,number:false,range:"number",search:"text",tel:false,time:false,url:false,week:false};a(document).bind("pagecreate create",function(e){var b=a(e.target).closest(':jqmData(role="page")').data("page"),d;if(b)d=b.options,a(e.target).find("input").not(b.keepNativeSelector()).each(function(){var b=a(this),c=this.getAttribute("type"),e=d.degradeInputs[c]||"text";
if(d.degradeInputs[c]){var g=a("<div>").html(b.clone()).html(),i=g.indexOf(" type=")>-1;b.replaceWith(g.replace(i?/\s+type=["']?\w+['"]?/:/\/?>/,' type="'+e+'" data-'+a.mobile.ns+'type="'+c+'"'+(i?"":">")))}})})})(jQuery);
(function(a,e){a.widget("mobile.dialog",a.mobile.widget,{options:{closeBtnText:"Close",overlayTheme:"a",initSelector:":jqmData(role='dialog')"},_create:function(){var b=this,d=this.element,f=a("<a href='#' data-"+a.mobile.ns+"icon='delete' data-"+a.mobile.ns+"iconpos='notext'>"+this.options.closeBtnText+"</a>");d.addClass("ui-overlay-"+this.options.overlayTheme);d.attr("role","dialog").addClass("ui-dialog").find(":jqmData(role='header')").addClass("ui-corner-top ui-overlay-shadow").prepend(f).end().find(":jqmData(role='content'),:jqmData(role='footer')").addClass("ui-overlay-shadow").last().addClass("ui-corner-bottom");
f.bind("vclick",function(){b.close()});d.bind("vclick submit",function(b){var b=a(b.target).closest(b.type==="vclick"?"a":"form"),f;b.length&&!b.jqmData("transition")&&(f=a.mobile.urlHistory.getActive()||{},b.attr("data-"+a.mobile.ns+"transition",f.transition||a.mobile.defaultDialogTransition).attr("data-"+a.mobile.ns+"direction","reverse"))}).bind("pagehide",function(){a(this).find("."+a.mobile.activeBtnClass).removeClass(a.mobile.activeBtnClass)})},close:function(){e.history.back()}});a(a.mobile.dialog.prototype.options.initSelector).live("pagecreate",
function(){a(this).dialog()})})(jQuery,this);
(function(a){a.mobile.page.prototype.options.backBtnText="Back";a.mobile.page.prototype.options.addBackBtn=false;a.mobile.page.prototype.options.backBtnTheme=null;a.mobile.page.prototype.options.headerTheme="a";a.mobile.page.prototype.options.footerTheme="a";a.mobile.page.prototype.options.contentTheme=null;a(":jqmData(role='page'), :jqmData(role='dialog')").live("pagecreate",function(){var e=a(this),b=e.data("page").options,d=e.jqmData("role"),f=b.theme;a(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')",
this).each(function(){var c=a(this),e=c.jqmData("role"),g=c.jqmData("theme"),i=g||b.contentTheme||d==="dialog"&&f,k;c.addClass("ui-"+e);if(e==="header"||e==="footer"){var l=g||(e==="header"?b.headerTheme:b.footerTheme)||f;c.addClass("ui-bar-"+l).attr("role",e==="header"?"banner":"contentinfo");g=c.children("a");i=g.hasClass("ui-btn-left");k=g.hasClass("ui-btn-right");i=i||g.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length;k||g.eq(1).addClass("ui-btn-right");b.addBackBtn&&e==="header"&&a(".ui-page").length>
1&&c.jqmData("url")!==a.mobile.path.stripHash(location.hash)&&!i&&a("<a href='#' class='ui-btn-left' data-"+a.mobile.ns+"rel='back' data-"+a.mobile.ns+"icon='arrow-l'>"+b.backBtnText+"</a>").attr("data-"+a.mobile.ns+"theme",b.backBtnTheme||l).prependTo(c);c.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({tabindex:"0",role:"heading","aria-level":"1"})}else e==="content"&&(i&&c.addClass("ui-body-"+i),c.attr("role","main"))})})})(jQuery);
(function(a){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:true,heading:"h1,h2,h3,h4,h5,h6,legend",theme:null,contentTheme:null,iconTheme:"d",initSelector:":jqmData(role='collapsible')"},_create:function(){var e=this.element,b=this.options,d=e.addClass("ui-collapsible"),f=e.children(b.heading).first(),c=d.wrapInner("<div class='ui-collapsible-content'></div>").find(".ui-collapsible-content"),
h=e.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set"),e=h.children(":jqmData(role='collapsible')");f.is("legend")&&(f=a("<div role='heading'>"+f.html()+"</div>").insertBefore(f),f.next().remove());if(h.length){if(!b.theme)b.theme=h.jqmData("theme");if(!b.contentTheme)b.contentTheme=h.jqmData("content-theme")}c.addClass(b.contentTheme?"ui-body-"+b.contentTheme:"");f.insertBefore(c).addClass("ui-collapsible-heading").append("<span class='ui-collapsible-heading-status'></span>").wrapInner("<a href='#' class='ui-collapsible-heading-toggle'></a>").find("a").first().buttonMarkup({shadow:false,
corners:false,iconPos:"left",icon:"plus",theme:b.theme});h.length?(h.jqmData("collapsiblebound")||h.jqmData("collapsiblebound",true).bind("expand",function(b){a(b.target).closest(".ui-collapsible").siblings(".ui-collapsible").trigger("collapse")}),e.first().find("a").first().addClass("ui-corner-top").find(".ui-btn-inner").addClass("ui-corner-top"),e.last().jqmData("collapsible-last",true).find("a").first().addClass("ui-corner-bottom").find(".ui-btn-inner").addClass("ui-corner-bottom"),d.jqmData("collapsible-last")&&
f.find("a").first().add(f.find(".ui-btn-inner")).addClass("ui-corner-bottom")):f.find("a").first().add(f.find(".ui-btn-inner")).addClass("ui-corner-top ui-corner-bottom");d.bind("expand collapse",function(e){if(!e.isDefaultPrevented()){e.preventDefault();var i=a(this),e=e.type==="collapse",k=b.contentTheme;f.toggleClass("ui-collapsible-heading-collapsed",e).find(".ui-collapsible-heading-status").text(e?b.expandCueText:b.collapseCueText).end().find(".ui-icon").toggleClass("ui-icon-minus",!e).toggleClass("ui-icon-plus",
e);i.toggleClass("ui-collapsible-collapsed",e);c.toggleClass("ui-collapsible-content-collapsed",e).attr("aria-hidden",e);if(k&&(!h.length||d.jqmData("collapsible-last")))f.find("a").first().add(f.find(".ui-btn-inner")).toggleClass("ui-corner-bottom",e),c.toggleClass("ui-corner-bottom",!e);c.trigger("updatelayout")}}).trigger(b.collapsed?"collapse":"expand");f.bind("click",function(a){var b=f.is(".ui-collapsible-heading-collapsed")?"expand":"collapse";d.trigger(b);a.preventDefault()})}});a(document).bind("pagecreate create",
function(e){a(a.mobile.collapsible.prototype.options.initSelector,e.target).collapsible()})})(jQuery);(function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain ui-body ui-br")};a(document).bind("pagecreate create",function(e){a(":jqmData(role='fieldcontain')",e.target).fieldcontain()})})(jQuery);
(function(a){a.fn.grid=function(e){return this.each(function(){var b=a(this),d=a.extend({grid:null},e),f=b.children(),c={solo:1,a:2,b:3,c:4,d:5},d=d.grid;if(!d)if(f.length<=5)for(var h in c)c[h]===f.length&&(d=h);else d="a";c=c[d];b.addClass("ui-grid-"+d);f.filter(":nth-child("+c+"n+1)").addClass("ui-block-a");c>1&&f.filter(":nth-child("+c+"n+2)").addClass("ui-block-b");c>2&&f.filter(":nth-child(3n+3)").addClass("ui-block-c");c>3&&f.filter(":nth-child(4n+4)").addClass("ui-block-d");c>4&&f.filter(":nth-child(5n+5)").addClass("ui-block-e")})}})(jQuery);
(function(a,e){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null,initSelector:":jqmData(role='navbar')"},_create:function(){var b=this.element,d=b.find("a"),f=d.filter(":jqmData(icon)").length?this.options.iconpos:e;b.addClass("ui-navbar").attr("role","navigation").find("ul").grid({grid:this.options.grid});f||b.addClass("ui-navbar-noicons");d.buttonMarkup({corners:false,shadow:false,iconpos:f});b.delegate("a","vclick",function(){d.not(".ui-state-persist").removeClass(a.mobile.activeBtnClass);
a(this).addClass(a.mobile.activeBtnClass)})}});a(document).bind("pagecreate create",function(b){a(a.mobile.navbar.prototype.options.initSelector,b.target).navbar()})})(jQuery);
(function(a){var e={};a.widget("mobile.listview",a.mobile.widget,{options:{theme:null,countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",inset:false,initSelector:":jqmData(role='listview')"},_create:function(){var a=this;a.element.addClass(function(d,f){return f+" ui-listview "+(a.options.inset?" ui-listview-inset ui-corner-all ui-shadow ":"")});a.refresh(true)},_removeCorners:function(a,d){a=a.add(a.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb"));d==="top"?a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl"):
d==="bottom"?a.removeClass("ui-corner-bottom ui-corner-br ui-corner-bl"):a.removeClass("ui-corner-top ui-corner-tr ui-corner-tl ui-corner-bottom ui-corner-br ui-corner-bl")},_refreshCorners:function(a){var d,f;this.options.inset&&(d=this.element.children("li"),f=a?d.not(".ui-screen-hidden"):d.filter(":visible"),this._removeCorners(d),d=f.first().addClass("ui-corner-top"),d.add(d.find(".ui-btn-inner").not(".ui-li-link-alt span:first-child")).addClass("ui-corner-top").end().find(".ui-li-link-alt, .ui-li-link-alt span:first-child").addClass("ui-corner-tr").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-tl"),
f=f.last().addClass("ui-corner-bottom"),f.add(f.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-bl"));a||this.element.trigger("updatelayout")},_findFirstElementByTagName:function(a,d,f,c){var e={};for(e[f]=e[c]=true;a;){if(e[a.nodeName])return a;a=a[d]}return null},_getChildrenByTagName:function(b,d,f){var c=[],e={};e[d]=e[f]=true;for(b=b.firstChild;b;)e[b.nodeName]&&c.push(b),b=b.nextSibling;return a(c)},_addThumbClasses:function(b){var d,
f,c=b.length;for(d=0;d<c;d++)f=a(this._findFirstElementByTagName(b[d].firstChild,"nextSibling","img","IMG")),f.length&&(f.addClass("ui-li-thumb"),a(this._findFirstElementByTagName(f[0].parentNode,"parentNode","li","LI")).addClass(f.is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb"))},refresh:function(b){this.parentPage=this.element.closest(".ui-page");this._createSubPages();var d=this.options,f=this.element,c=f.jqmData("dividertheme")||d.dividerTheme,e=f.jqmData("splittheme"),g=f.jqmData("spliticon"),
i=this._getChildrenByTagName(f[0],"li","LI"),k=a.support.cssPseudoElement||!a.nodeName(f[0],"ol")?0:1,l={},o,m,p,j,q;k&&f.find(".ui-li-dec").remove();if(!d.theme)d.theme=a.mobile.getInheritedTheme(this.element,"c");for(var n=0,A=i.length;n<A;n++){o=i.eq(n);m="ui-li";if(b||!o.hasClass("ui-li"))p=o.jqmData("theme")||d.theme,j=this._getChildrenByTagName(o[0],"a","A"),j.length?(q=o.jqmData("icon"),o.buttonMarkup({wrapperEls:"div",shadow:false,corners:false,iconpos:"right",icon:j.length>1||q===false?false:
q||"arrow-r",theme:p}),q!=false&&j.length==1&&o.addClass("ui-li-has-arrow"),j.first().addClass("ui-link-inherit"),j.length>1&&(m+=" ui-li-has-alt",j=j.last(),q=e||j.jqmData("theme")||d.splitTheme,j.appendTo(o).attr("title",j.getEncodedText()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:false,corners:false,theme:p,icon:false,iconpos:false}).find(".ui-btn-inner").append(a(document.createElement("span")).buttonMarkup({shadow:true,corners:true,theme:q,iconpos:"notext",icon:g||j.jqmData("icon")||
d.splitIcon})))):o.jqmData("role")==="list-divider"?(m+=" ui-li-divider ui-btn ui-bar-"+c,o.attr("role","heading"),k&&(k=1)):m+=" ui-li-static ui-body-"+p;k&&m.indexOf("ui-li-divider")<0&&(p=o.is(".ui-li-static:first")?o:o.find(".ui-link-inherit"),p.addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+k++ +". </span>"));l[m]||(l[m]=[]);l[m].push(o[0])}for(m in l)a(l[m]).addClass(m).children(".ui-btn-inner").addClass(m);f.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end().find("p, dl").addClass("ui-li-desc").end().find(".ui-li-aside").each(function(){var b=
a(this);b.prependTo(b.parent())}).end().find(".ui-li-count").each(function(){a(this).closest("li").addClass("ui-li-has-count")}).addClass("ui-btn-up-"+(f.jqmData("counttheme")||this.options.countTheme)+" ui-btn-corner-all");this._addThumbClasses(i);this._addThumbClasses(f.find(".ui-link-inherit"));this._refreshCorners(b)},_idStringEscape:function(a){return a.replace(/[^a-zA-Z0-9]/g,"-")},_createSubPages:function(){var b=this.element,d=b.closest(".ui-page"),f=d.jqmData("url"),c=f||d[0][a.expando],
h=b.attr("id"),g=this.options,i="data-"+a.mobile.ns,k=this,l=d.find(":jqmData(role='footer')").jqmData("id"),o;typeof e[c]==="undefined"&&(e[c]=-1);h=h||++e[c];a(b.find("li>ul, li>ol").toArray().reverse()).each(function(c){var d=a(this),e=d.attr("id")||h+"-"+c,c=d.parent(),k=a(d.prevAll().toArray().reverse()),k=k.length?k:a("<span>"+a.trim(c.contents()[0].nodeValue)+"</span>"),n=k.first().getEncodedText(),e=(f||"")+"&"+a.mobile.subPageUrlKey+"="+e,A=d.jqmData("theme")||g.theme,z=d.jqmData("counttheme")||
b.jqmData("counttheme")||g.countTheme;o=true;d.detach().wrap("<div "+i+"role='page' "+i+"url='"+e+"' "+i+"theme='"+A+"' "+i+"count-theme='"+z+"'><div "+i+"role='content'></div></div>").parent().before("<div "+i+"role='header' "+i+"theme='"+g.headerTheme+"'><div class='ui-title'>"+n+"</div></div>").after(l?a("<div "+i+"role='footer' "+i+"id='"+l+"'>"):"").parent().appendTo(a.mobile.pageContainer).page();d=c.find("a:first");d.length||(d=a("<a/>").html(k||n).prependTo(c.empty()));d.attr("href","#"+e)}).listview();
o&&d.is(":jqmData(external-page='true')")&&d.data("page").options.domCache===false&&d.unbind("pagehide.remove").bind("pagehide.remove",function(b,c){var e=c.nextPage;c.nextPage&&(e=e.jqmData("url"),e.indexOf(f+"&"+a.mobile.subPageUrlKey)!==0&&(k.childPages().remove(),d.remove()))})},childPages:function(){var b=this.parentPage.jqmData("url");return a(":jqmData(url^='"+b+"&"+a.mobile.subPageUrlKey+"')")}});a(document).bind("pagecreate create",function(b){a(a.mobile.listview.prototype.options.initSelector,
b.target).listview()})})(jQuery);
(function(a){a.mobile.listview.prototype.options.filter=false;a.mobile.listview.prototype.options.filterPlaceholder="Filter items...";a.mobile.listview.prototype.options.filterTheme="c";a.mobile.listview.prototype.options.filterCallback=function(a,b){return a.toLowerCase().indexOf(b)===-1};a(":jqmData(role='listview')").live("listviewcreate",function(){var e=a(this),b=e.data("listview");if(b.options.filter){var d=a("<form>",{"class":"ui-listview-filter ui-bar-"+b.options.filterTheme,role:"search"});
a("<input>",{placeholder:b.options.filterPlaceholder}).attr("data-"+a.mobile.ns+"type","search").jqmData("lastval","").bind("keyup change",function(){var d=a(this),c=this.value.toLowerCase(),h=null,h=d.jqmData("lastval")+"",g=false,i="";d.jqmData("lastval",c);i=c.substr(0,h.length-1).replace(h,"");h=c.length<h.length||i.length!=c.length-h.length?e.children():e.children(":not(.ui-screen-hidden)");if(c){for(var k=h.length-1;k>=0;k--)d=a(h[k]),i=d.jqmData("filtertext")||d.text(),d.is("li:jqmData(role=list-divider)")?
(d.toggleClass("ui-filter-hidequeue",!g),g=false):b.options.filterCallback(i,c)?d.toggleClass("ui-filter-hidequeue",true):g=true;h.filter(":not(.ui-filter-hidequeue)").toggleClass("ui-screen-hidden",false);h.filter(".ui-filter-hidequeue").toggleClass("ui-screen-hidden",true).toggleClass("ui-filter-hidequeue",false)}else h.toggleClass("ui-screen-hidden",false);b._refreshCorners()}).appendTo(d).textinput();a(this).jqmData("inset")&&d.addClass("ui-listview-filter-inset");d.bind("submit",function(){return false}).insertBefore(e)}})})(jQuery);
(function(a){a(document).bind("pagecreate create",function(e){a(":jqmData(role='nojs')",e.target).addClass("ui-nojs")})})(jQuery);
(function(a,e){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null,initSelector:"input[type='checkbox'],input[type='radio']"},_create:function(){var b=this,d=this.element,f=d.closest("form,fieldset,:jqmData(role='page')").find("label[for='"+d[0].id+"']"),c=d.attr("type"),h=c+"-on",g=c+"-off",i=d.parents(":jqmData(type='horizontal')").length?e:g;if(!(c!=="checkbox"&&c!=="radio")){a.extend(this,{label:f,inputtype:c,checkedClass:"ui-"+h+(i?"":" "+a.mobile.activeBtnClass),uncheckedClass:"ui-"+
g,checkedicon:"ui-icon-"+h,uncheckedicon:"ui-icon-"+g});if(!this.options.theme)this.options.theme=this.element.jqmData("theme");f.buttonMarkup({theme:this.options.theme,icon:i,shadow:false});d.add(f).wrapAll("<div class='ui-"+c+"'></div>");f.bind({vmouseover:function(b){a(this).parent().is(".ui-disabled")&&b.stopPropagation()},vclick:function(a){if(d.is(":disabled"))a.preventDefault();else return b._cacheVals(),d.prop("checked",c==="radio"&&true||!d.prop("checked")),d.triggerHandler("click"),b._getInputSet().not(d).prop("checked",
false),b._updateAll(),false}});d.bind({vmousedown:function(){b._cacheVals()},vclick:function(){var c=a(this);c.is(":checked")?(c.prop("checked",true),b._getInputSet().not(c).prop("checked",false)):c.prop("checked",false);b._updateAll()},focus:function(){f.addClass("ui-focus")},blur:function(){f.removeClass("ui-focus")}});this.refresh()}},_cacheVals:function(){this._getInputSet().each(function(){var b=a(this);b.jqmData("cacheVal",b.is(":checked"))})},_getInputSet:function(){return this.inputtype==
"checkbox"?this.element:this.element.closest("form,fieldset,:jqmData(role='page')").find("input[name='"+this.element.attr("name")+"'][type='"+this.inputtype+"']")},_updateAll:function(){var b=this;this._getInputSet().each(function(){var d=a(this);(d.is(":checked")||b.inputtype==="checkbox")&&d.trigger("change")}).checkboxradio("refresh")},refresh:function(){var b=this.element,d=this.label,f=d.find(".ui-icon");a(b[0]).prop("checked")?(d.addClass(this.checkedClass).removeClass(this.uncheckedClass),
f.addClass(this.checkedicon).removeClass(this.uncheckedicon)):(d.removeClass(this.checkedClass).addClass(this.uncheckedClass),f.removeClass(this.checkedicon).addClass(this.uncheckedicon));b.is(":disabled")?this.disable():this.enable()},disable:function(){this.element.prop("disabled",true).parent().addClass("ui-disabled")},enable:function(){this.element.prop("disabled",false).parent().removeClass("ui-disabled")}});a(document).bind("pagecreate create",function(b){a.mobile.checkboxradio.prototype.enhanceWithin(b.target)})})(jQuery);
(function(a,e){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,inline:null,corners:true,shadow:true,iconshadow:true,initSelector:"button, [type='button'], [type='submit'], [type='reset'], [type='image']"},_create:function(){var b=this.element,d=this.options,f,c;this.button=a("<div></div>").text(b.text()||b.val()).insertBefore(b).buttonMarkup({theme:d.theme,icon:d.icon,iconpos:d.iconpos,inline:d.inline,corners:d.corners,shadow:d.shadow,iconshadow:d.iconshadow}).append(b.addClass("ui-btn-hidden"));
d=b.attr("type");f=b.attr("name");d!=="button"&&d!=="reset"&&f&&b.bind("vclick",function(){c===e&&(c=a("<input>",{type:"hidden",name:b.attr("name"),value:b.attr("value")}).insertBefore(b),a(document).one("submit",function(){c.remove();c=e}))});this.refresh()},enable:function(){this.element.attr("disabled",false);this.button.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.button.addClass("ui-disabled").attr("aria-disabled",
true);return this._setOption("disabled",true)},refresh:function(){var a=this.element;a.prop("disabled")?this.disable():this.enable();this.button.data("textWrapper").text(a.text()||a.val())}});a(document).bind("pagecreate create",function(b){a.mobile.button.prototype.enhanceWithin(b.target)})})(jQuery);
(function(a,e){a.widget("mobile.slider",a.mobile.widget,{options:{theme:null,trackTheme:null,disabled:false,initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')"},_create:function(){var b=this,d=this.element,f=a.mobile.getInheritedTheme(d,"c"),c=this.options.theme||f,h=this.options.trackTheme||f,g=d[0].nodeName.toLowerCase(),f=g=="select"?"ui-slider-switch":"",i=d.attr("id"),k=i+"-label",i=a("[for='"+i+"']").attr("id",k),l=function(){return g=="input"?parseFloat(d.val()):
d[0].selectedIndex},o=g=="input"?parseFloat(d.attr("min")):0,m=g=="input"?parseFloat(d.attr("max")):d.find("option").length-1,p=window.parseFloat(d.attr("step")||1),j=a("<div class='ui-slider "+f+" ui-btn-down-"+h+" ui-btn-corner-all' role='application'></div>"),q=a("<a href='#' class='ui-slider-handle'></a>").appendTo(j).buttonMarkup({corners:true,theme:c,shadow:true}).attr({role:"slider","aria-valuemin":o,"aria-valuemax":m,"aria-valuenow":l(),"aria-valuetext":l(),title:l(),"aria-labelledby":k});
a.extend(this,{slider:j,handle:q,dragging:false,beforeStart:null,userModified:false,mouseMoved:false});g=="select"&&(j.wrapInner("<div class='ui-slider-inneroffset'></div>"),q.addClass("ui-slider-handle-snapping"),d.find("option"),d.find("option").each(function(b){var c=!b?"b":"a",d=!b?"right":"left",b=!b?" ui-btn-down-"+h:" "+a.mobile.activeBtnClass;a("<div class='ui-slider-labelbg ui-slider-labelbg-"+c+b+" ui-btn-corner-"+d+"'></div>").prependTo(j);a("<span class='ui-slider-label ui-slider-label-"+
c+b+" ui-btn-corner-"+d+"' role='img'>"+a(this).getEncodedText()+"</span>").prependTo(q)}));i.addClass("ui-slider");d.addClass(g==="input"?"ui-slider-input":"ui-slider-switch").change(function(){b.mouseMoved||b.refresh(l(),true)}).keyup(function(){b.refresh(l(),true,true)}).blur(function(){b.refresh(l(),true)});a(document).bind("vmousemove",function(a){if(b.dragging)return b.mouseMoved=true,g==="select"&&q.removeClass("ui-slider-handle-snapping"),b.refresh(a),b.userModified=b.beforeStart!==d[0].selectedIndex,
false});j.bind("vmousedown",function(a){b.dragging=true;b.userModified=false;b.mouseMoved=false;if(g==="select")b.beforeStart=d[0].selectedIndex;b.refresh(a);return false});j.add(document).bind("vmouseup",function(){if(b.dragging)return b.dragging=false,g==="select"&&(q.addClass("ui-slider-handle-snapping"),b.mouseMoved?b.userModified?b.refresh(b.beforeStart==0?1:0):b.refresh(b.beforeStart):b.refresh(b.beforeStart==0?1:0)),b.mouseMoved=false});j.insertAfter(d);this.handle.bind("vmousedown",function(){a(this).focus()}).bind("vclick",
false);this.handle.bind("keydown",function(c){var d=l();if(!b.options.disabled){switch(c.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:if(c.preventDefault(),!b._keySliding)b._keySliding=true,a(this).addClass("ui-state-active")}switch(c.keyCode){case a.mobile.keyCode.HOME:b.refresh(o);break;case a.mobile.keyCode.END:b.refresh(m);
break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:b.refresh(d+p);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.refresh(d-p)}}}).keyup(function(){if(b._keySliding)b._keySliding=false,a(this).removeClass("ui-state-active")});this.refresh(e,e,true)},refresh:function(a,d,f){(this.options.disabled||this.element.attr("disabled"))&&this.disable();var c=this.element,e,g=c[0].nodeName.toLowerCase(),i=g==="input"?parseFloat(c.attr("min")):
0,k=g==="input"?parseFloat(c.attr("max")):c.find("option").length-1;if(typeof a==="object"){if(!this.dragging||a.pageX<this.slider.offset().left-8||a.pageX>this.slider.offset().left+this.slider.width()+8)return;e=Math.round((a.pageX-this.slider.offset().left)/this.slider.width()*100)}else a==null&&(a=g==="input"?parseFloat(c.val()):c[0].selectedIndex),e=(parseFloat(a)-i)/(k-i)*100;if(!isNaN(e)&&(e<0&&(e=0),e>100&&(e=100),a=Math.round(e/100*(k-i))+i,a<i&&(a=i),a>k&&(a=k),this.handle.css("left",e+"%"),
this.handle.attr({"aria-valuenow":g==="input"?a:c.find("option").eq(a).attr("value"),"aria-valuetext":g==="input"?a:c.find("option").eq(a).getEncodedText(),title:a}),g==="select"&&(a===0?this.slider.addClass("ui-slider-switch-a").removeClass("ui-slider-switch-b"):this.slider.addClass("ui-slider-switch-b").removeClass("ui-slider-switch-a")),!f))f=false,g==="input"?(f=c.val()!==a,c.val(a)):(f=c[0].selectedIndex!==a,c[0].selectedIndex=a),!d&&f&&c.trigger("change")},enable:function(){this.element.attr("disabled",
false);this.slider.removeClass("ui-disabled").attr("aria-disabled",false);return this._setOption("disabled",false)},disable:function(){this.element.attr("disabled",true);this.slider.addClass("ui-disabled").attr("aria-disabled",true);return this._setOption("disabled",true)}});a(document).bind("pagecreate create",function(b){a.mobile.slider.prototype.enhanceWithin(b.target)})})(jQuery);
(function(a){a.widget("mobile.textinput",a.mobile.widget,{options:{theme:null,initSelector:"input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])"},_create:function(){var e=this.element,
b=this.options.theme||a.mobile.getInheritedTheme(this.element,"c"),d=" ui-body-"+b,f,c;a("label[for='"+e.attr("id")+"']").addClass("ui-input-text");f=e.addClass("ui-input-text ui-body-"+b);typeof e[0].autocorrect!=="undefined"&&!a.support.touchOverflow&&(e[0].setAttribute("autocorrect","off"),e[0].setAttribute("autocomplete","off"));e.is("[type='search'],:jqmData(type='search')")?(f=e.wrap("<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield"+d+"'></div>").parent(),
c=a("<a href='#' class='ui-input-clear' title='clear text'>clear text</a>").tap(function(a){e.val("").focus();e.trigger("change");c.addClass("ui-input-clear-hidden");a.preventDefault()}).appendTo(f).buttonMarkup({icon:"delete",iconpos:"notext",corners:true,shadow:true}),b=function(){setTimeout(function(){c.toggleClass("ui-input-clear-hidden",!e.val())},0)},b(),e.bind("paste cut keyup focus change blur",b)):e.addClass("ui-corner-all ui-shadow-inset"+d);e.focus(function(){f.addClass("ui-focus")}).blur(function(){f.removeClass("ui-focus")});
if(e.is("textarea")){var h=function(){var a=e[0].scrollHeight;e[0].clientHeight<a&&e.height(a+15)},g;e.keyup(function(){clearTimeout(g);g=setTimeout(h,100)});a.trim(e.val())&&(a(window).load(h),a(document).one("pagechange",h))}},disable:function(){(this.element.attr("disabled",true).is("[type='search'],:jqmData(type='search')")?this.element.parent():this.element).addClass("ui-disabled")},enable:function(){(this.element.attr("disabled",false).is("[type='search'],:jqmData(type='search')")?this.element.parent():
this.element).removeClass("ui-disabled")}});a(document).bind("pagecreate create",function(e){a.mobile.textinput.prototype.enhanceWithin(e.target)})})(jQuery);
(function(a){var e=function(b){var d=b.selectID,f=b.label,c=b.select.closest(".ui-page"),e=a("<div>",{"class":"ui-selectmenu-screen ui-screen-hidden"}).appendTo(c),g=b._selectOptions(),i=b.isMultiple=b.select[0].multiple,k=d+"-button",l=d+"-menu",o=a("<div data-"+a.mobile.ns+"role='dialog' data-"+a.mobile.ns+"theme='"+b.options.theme+"' data-"+a.mobile.ns+"overlay-theme='"+b.options.overlayTheme+"'><div data-"+a.mobile.ns+"role='header'><div class='ui-title'>"+f.getEncodedText()+"</div></div><div data-"+
a.mobile.ns+"role='content'></div></div>").appendTo(a.mobile.pageContainer).page(),m=a("<div>",{"class":"ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-"+b.options.overlayTheme+" "+a.mobile.defaultDialogTransition}).insertAfter(e),p=a("<ul>",{"class":"ui-selectmenu-list",id:l,role:"listbox","aria-labelledby":k}).attr("data-"+a.mobile.ns+"theme",b.options.theme).appendTo(m),j=a("<div>",{"class":"ui-header ui-bar-"+b.options.theme}).prependTo(m),q=a("<h1>",{"class":"ui-title"}).appendTo(j),
n=a("<a>",{text:b.options.closeText,href:"#","class":"ui-btn-left"}).attr("data-"+a.mobile.ns+"iconpos","notext").attr("data-"+a.mobile.ns+"icon","delete").appendTo(j).buttonMarkup(),A=o.find(".ui-content"),z=o.find(".ui-header a");a.extend(b,{select:b.select,selectID:d,buttonId:k,menuId:l,thisPage:c,menuPage:o,label:f,screen:e,selectOptions:g,isMultiple:i,theme:b.options.theme,listbox:m,list:p,header:j,headerTitle:q,headerClose:n,menuPageContent:A,menuPageClose:z,placeholder:"",build:function(){var b=
this;b.refresh();b.select.attr("tabindex","-1").focus(function(){a(this).blur();b.button.focus()});b.button.bind("vclick keydown",function(c){if(c.type=="vclick"||c.keyCode&&(c.keyCode===a.mobile.keyCode.ENTER||c.keyCode===a.mobile.keyCode.SPACE))b.open(),c.preventDefault()});b.list.attr("role","listbox").delegate(".ui-li>a","focusin",function(){a(this).attr("tabindex","0")}).delegate(".ui-li>a","focusout",function(){a(this).attr("tabindex","-1")}).delegate("li:not(.ui-disabled, .ui-li-divider)",
"click",function(c){var d=b.select[0].selectedIndex,f=b.list.find("li:not(.ui-li-divider)").index(this),e=b._selectOptions().eq(f)[0];e.selected=b.isMultiple?!e.selected:true;b.isMultiple&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",e.selected).toggleClass("ui-icon-checkbox-off",!e.selected);(b.isMultiple||d!==f)&&b.select.trigger("change");b.isMultiple||b.close();c.preventDefault()}).keydown(function(b){var c=a(b.target),d=c.closest("li");switch(b.keyCode){case 38:return b=d.prev(),
b.length&&(c.blur().attr("tabindex","-1"),b.find("a").first().focus()),false;case 40:return b=d.next(),b.length&&(c.blur().attr("tabindex","-1"),b.find("a").first().focus()),false;case 13:case 32:return c.trigger("click"),false}});b.menuPage.bind("pagehide",function(){b.list.appendTo(b.listbox);b._focusButton();a.mobile._bindPageRemove.call(b.thisPage)});b.screen.bind("vclick",function(){b.close()});b.headerClose.click(function(){if(b.menuType=="overlay")return b.close(),false});b.thisPage.addDependents(this.menuPage)},
_isRebuildRequired:function(){var a=this.list.find("li");return this._selectOptions().text()!==a.text()},refresh:function(b){var c=this;this._selectOptions();this.selected();var d=this.selectedIndices();(b||this._isRebuildRequired())&&c._buildList();c.setButtonText();c.setButtonCount();c.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected",false).each(function(b){a.inArray(b,d)>-1&&(b=a(this),b.attr("aria-selected",true),c.isMultiple?b.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on"):
b.addClass(a.mobile.activeBtnClass))})},close:function(){if(!this.options.disabled&&this.isOpen)this.menuType=="page"?window.history.back():(this.screen.addClass("ui-screen-hidden"),this.listbox.addClass("ui-selectmenu-hidden").removeAttr("style").removeClass("in"),this.list.appendTo(this.listbox),this._focusButton()),this.isOpen=false},open:function(){if(!this.options.disabled){var b=this,c=b.list.parent().outerHeight(),d=b.list.parent().outerWidth(),f=a(".ui-page-active"),e=a.support.touchOverflow&&
a.mobile.touchOverflowEnabled,f=f.is(".ui-native-fixed")?f.find(".ui-content"):f;scrollTop=e?f.scrollTop():a(window).scrollTop();btnOffset=b.button.offset().top;screenHeight=window.innerHeight;screenWidth=window.innerWidth;b.button.addClass(a.mobile.activeBtnClass);setTimeout(function(){b.button.removeClass(a.mobile.activeBtnClass)},300);if(c>screenHeight-80||!a.support.scrollTop){b.thisPage.unbind("pagehide.remove");if(scrollTop==0&&btnOffset>screenHeight)b.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",
btnOffset)});b.menuPage.one("pageshow",function(){a(window).one("silentscroll",function(){b.list.find(a.mobile.activeBtnClass).focus()});b.isOpen=true});b.menuType="page";b.menuPageContent.append(b.list);b.menuPage.find("div .ui-title").text(b.label.text());a.mobile.changePage(b.menuPage,{transition:a.mobile.defaultDialogTransition})}else{b.menuType="overlay";b.screen.height(a(document).height()).removeClass("ui-screen-hidden");var f=btnOffset-scrollTop,h=scrollTop+screenHeight-btnOffset,g=c/2,e=
parseFloat(b.list.parent().css("max-width")),c=f>c/2&&h>c/2?btnOffset+b.button.outerHeight()/2-g:f>h?scrollTop+screenHeight-c-30:scrollTop+30;d<e?e=(screenWidth-d)/2:(e=b.button.offset().left+b.button.outerWidth()/2-d/2,e<30?e=30:e+d>screenWidth&&(e=screenWidth-d-30));b.listbox.append(b.list).removeClass("ui-selectmenu-hidden").css({top:c,left:e}).addClass("in");b.list.find(a.mobile.activeBtnClass).focus();b.isOpen=true}}},_buildList:function(){var b=this,c=this.options,d=this.placeholder,f=[],e=
[],h=b.isMultiple?"checkbox-off":"false";b.list.empty().filter(".ui-listview").listview("destroy");b.select.find("option").each(function(g){var j=a(this),i=j.parent(),m=j.getEncodedText(),p="<a href='#'>"+m+"</a>",k=[],n=[];i.is("optgroup")&&(i=i.attr("label"),a.inArray(i,f)===-1&&(e.push("<li data-"+a.mobile.ns+"role='list-divider'>"+i+"</li>"),f.push(i)));if(!this.getAttribute("value")||m.length==0||j.jqmData("placeholder"))c.hidePlaceholderMenuItems&&k.push("ui-selectmenu-placeholder"),d=b.placeholder=
m;this.disabled&&(k.push("ui-disabled"),n.push("aria-disabled='true'"));e.push("<li data-"+a.mobile.ns+"option-index='"+g+"' data-"+a.mobile.ns+"icon='"+h+"' class='"+k.join(" ")+"' "+n.join(" ")+">"+p+"</li>")});b.list.html(e.join(" "));b.list.find("li").attr({role:"option",tabindex:"-1"}).first().attr("tabindex","0");this.isMultiple||this.headerClose.hide();!this.isMultiple&&!d.length?this.header.hide():this.headerTitle.text(this.placeholder);b.list.listview()},_button:function(){return a("<a>",
{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})}})};a("select").live("selectmenubeforecreate",function(){var b=a(this).data("selectmenu");b.options.nativeMenu||e(b)})})(jQuery);
(function(a){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:false,icon:"arrow-d",iconpos:"right",inline:null,corners:true,shadow:true,iconshadow:true,menuPageTheme:"b",overlayTheme:"a",hidePlaceholderMenuItems:true,closeText:"Close",nativeMenu:true,initSelector:"select:not(:jqmData(role='slider'))"},_button:function(){return a("<div/>")},_setDisabled:function(a){this.element.attr("disabled",a);this.button.attr("aria-disabled",a);return this._setOption("disabled",a)},_focusButton:function(){var a=
this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},_preExtension:function(){this.select=this.element.wrap("<div class='ui-select'>");this.selectID=this.select.attr("id");this.label=a("label[for='"+this.selectID+"']").addClass("ui-select");this.isMultiple=this.select[0].multiple;if(!this.options.theme)this.options.theme=a.mobile.getInheritedTheme(this.select,"c")},_create:function(){this._preExtension();this._trigger("beforeCreate");this.button=
this._button();var e=this,b=this.options,d=this.button.text(a(this.select[0].options.item(this.select[0].selectedIndex==-1?0:this.select[0].selectedIndex)).text()).insertBefore(this.select).buttonMarkup({theme:b.theme,icon:b.icon,iconpos:b.iconpos,inline:b.inline,corners:b.corners,shadow:b.shadow,iconshadow:b.iconshadow});b.nativeMenu&&window.opera&&window.opera.version&&this.select.addClass("ui-select-nativeonly");if(this.isMultiple)this.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(d.addClass("ui-li-has-count"));
(b.disabled||this.element.attr("disabled"))&&this.disable();this.select.change(function(){e.refresh()});this.build()},build:function(){var e=this;this.select.appendTo(e.button).bind("vmousedown",function(){e.button.addClass(a.mobile.activeBtnClass)}).bind("focus vmouseover",function(){e.button.trigger("vmouseover")}).bind("vmousemove",function(){e.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){e.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}).bind("change blur",
function(){e.button.removeClass("ui-btn-down-"+e.options.theme)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var e=this,b=this.selected();this.button.find(".ui-btn-text").text(function(){return!e.isMultiple?b.text():b.length?b.map(function(){return a(this).text()}).get().join(", "):e.placeholder})},setButtonCount:function(){var a=
this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},refresh:function(){this.setButtonText();this.setButtonCount()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(true);this.button.addClass("ui-disabled")},enable:function(){this._setDisabled(false);this.button.removeClass("ui-disabled")}});a(document).bind("pagecreate create",function(e){a.mobile.selectmenu.prototype.enhanceWithin(e.target)})})(jQuery);
(function(a,e){function b(b){for(var c;b;){if((c=typeof b.className==="string"&&b.className.split(" "))&&a.inArray("ui-btn",c)>-1&&a.inArray("ui-disabled",c)<0)break;b=b.parentNode}return b}a.fn.buttonMarkup=function(b){for(var b=b||{},c=0;c<this.length;c++){var h=this.eq(c),g=h[0],i=a.extend({},a.fn.buttonMarkup.defaults,{icon:b.icon!==e?b.icon:h.jqmData("icon"),iconpos:b.iconpos!==e?b.iconpos:h.jqmData("iconpos"),theme:b.theme!==e?b.theme:h.jqmData("theme"),inline:b.inline!==e?b.inline:h.jqmData("inline"),
shadow:b.shadow!==e?b.shadow:h.jqmData("shadow"),corners:b.corners!==e?b.corners:h.jqmData("corners"),iconshadow:b.iconshadow!==e?b.iconshadow:h.jqmData("iconshadow")},b),k="ui-btn-inner",l,o,m=document.createElement(i.wrapperEls),p=document.createElement(i.wrapperEls),j=i.icon?document.createElement("span"):null;d&&d();if(!i.theme)i.theme=a.mobile.getInheritedTheme(h,"c");l="ui-btn ui-btn-up-"+i.theme;i.inline&&(l+=" ui-btn-inline");if(i.icon)i.icon="ui-icon-"+i.icon,i.iconpos=i.iconpos||"left",
o="ui-icon "+i.icon,i.iconshadow&&(o+=" ui-icon-shadow");i.iconpos&&(l+=" ui-btn-icon-"+i.iconpos,i.iconpos=="notext"&&!h.attr("title")&&h.attr("title",h.getEncodedText()));i.corners&&(l+=" ui-btn-corner-all",k+=" ui-btn-corner-all");i.shadow&&(l+=" ui-shadow");g.setAttribute("data-"+a.mobile.ns+"theme",i.theme);h.addClass(l);m.className=k;m.setAttribute("aria-hidden","true");p.className="ui-btn-text";m.appendChild(p);if(j)j.className=o,m.appendChild(j);for(;g.firstChild;)p.appendChild(g.firstChild);
g.appendChild(m);a.data(g,"textWrapper",a(p))}return this};a.fn.buttonMarkup.defaults={corners:true,shadow:true,iconshadow:true,inline:false,wrapperEls:"span"};var d=function(){a(document).bind({vmousedown:function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-up-"+c).addClass("ui-btn-down-"+c))},"vmousecancel vmouseup":function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-down-"+c).addClass("ui-btn-up-"+
c))},"vmouseover focus":function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-up-"+c).addClass("ui-btn-hover-"+c))},"vmouseout blur":function(d){var d=b(d.target),c;d&&(d=a(d),c=d.attr("data-"+a.mobile.ns+"theme"),d.removeClass("ui-btn-hover-"+c+" ui-btn-down-"+c).addClass("ui-btn-up-"+c))}});d=null};a(document).bind("pagecreate create",function(b){a(":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a",
b.target).not(".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").buttonMarkup()})})(jQuery);
(function(a){a.fn.controlgroup=function(e){return this.each(function(){function b(a){a.removeClass("ui-btn-corner-all ui-shadow").eq(0).addClass(h[0]).end().last().addClass(h[1]).addClass("ui-controlgroup-last")}var d=a(this),f=a.extend({direction:d.jqmData("type")||"vertical",shadow:false,excludeInvisible:true},e),c=d.children("legend"),h=f.direction=="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"];d.find("input").first().attr("type");c.length&&(d.wrapInner("<div class='ui-controlgroup-controls'></div>"),
a("<div role='heading' class='ui-controlgroup-label'>"+c.html()+"</div>").insertBefore(d.children(0)),c.remove());d.addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+f.direction);b(d.find(".ui-btn"+(f.excludeInvisible?":visible":"")));b(d.find(".ui-btn-inner"));f.shadow&&d.addClass("ui-shadow")})};a(document).bind("pagecreate create",function(e){a(":jqmData(role='controlgroup')",e.target).controlgroup({excludeInvisible:false})})})(jQuery);
(function(a){a(document).bind("pagecreate create",function(e){a(e.target).find("a").not(".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")})})(jQuery);
(function(a,e){a.fn.fixHeaderFooter=function(){return!a.support.scrollTop||a.support.touchOverflow&&a.mobile.touchOverflowEnabled?this:this.each(function(){var b=a(this);b.jqmData("fullscreen")&&b.addClass("ui-page-fullscreen");b.find(".ui-header:jqmData(position='fixed')").addClass("ui-header-fixed ui-fixed-inline fade");b.find(".ui-footer:jqmData(position='fixed')").addClass("ui-footer-fixed ui-fixed-inline fade")})};a.mobile.fixedToolbars=function(){function b(){!i&&g==="overlay"&&(h||a.mobile.fixedToolbars.hide(true),
a.mobile.fixedToolbars.startShowTimer())}function d(a){var b=0,c,d;if(a){d=document.body;c=a.offsetParent;for(b=a.offsetTop;a&&a!=d;){b+=a.scrollTop||0;if(a==c)b+=c.offsetTop,c=a.offsetParent;a=a.parentNode}}return b}function f(b){var c=a(window).scrollTop(),e=d(b[0]),f=b.css("top")=="auto"?0:parseFloat(b.css("top")),h=window.innerHeight,g=b.outerHeight(),i=b.parents(".ui-page:not(.ui-page-fullscreen)").length;return b.is(".ui-header-fixed")?(f=c-e+f,f<e&&(f=0),b.css("top",i?f:c)):b.css("top",i?c+
h-g-(e-f):c+h-g)}if(a.support.scrollTop&&(!a.support.touchOverflow||!a.mobile.touchOverflowEnabled)){var c,h,g="inline",i=false,k=null,l=false,o=true;a(function(){var c=a(document),d=a(window);c.bind("vmousedown",function(){o&&(k=g)}).bind("vclick",function(b){o&&!a(b.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length&&!l&&(a.mobile.fixedToolbars.toggle(k),k=null)}).bind("silentscroll",b);(c.scrollTop()===0?d:c).bind("scrollstart",function(){l=true;k===
null&&(k=g);var b=k=="overlay";if(i=b||!!h)a.mobile.fixedToolbars.clearShowTimer(),b&&a.mobile.fixedToolbars.hide(true)}).bind("scrollstop",function(b){a(b.target).closest("a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed").length||(l=false,i&&(a.mobile.fixedToolbars.startShowTimer(),i=false),k=null)});d.bind("resize updatelayout",b)});a(".ui-page").live("pagebeforeshow",function(b,d){var e=a(b.target).find(":jqmData(role='footer')"),h=e.data("id"),g=d.prevPage,g=g&&g.find(":jqmData(role='footer')"),
g=g.length&&g.jqmData("id")===h;h&&g&&(c=e,f(c.removeClass("fade in out").appendTo(a.mobile.pageContainer)))}).live("pageshow",function(){var b=a(this);c&&c.length&&setTimeout(function(){f(c.appendTo(b).addClass("fade"));c=null},500);a.mobile.fixedToolbars.show(true,this)});a(".ui-collapsible-contain").live("collapse expand",b);return{show:function(b,c){a.mobile.fixedToolbars.clearShowTimer();g="overlay";return(c?a(c):a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var c=
a(this),e=a(window).scrollTop(),h=d(c[0]),g=window.innerHeight,i=c.outerHeight(),e=c.is(".ui-header-fixed")&&e<=h+i||c.is(".ui-footer-fixed")&&h<=e+g;c.addClass("ui-fixed-overlay").removeClass("ui-fixed-inline");!e&&!b&&c.animationComplete(function(){c.removeClass("in")}).addClass("in");f(c)})},hide:function(b){g="inline";return(a.mobile.activePage?a.mobile.activePage:a(".ui-page-active")).children(".ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last").each(function(){var c=a(this),
d=c.css("top"),d=d=="auto"?0:parseFloat(d);c.addClass("ui-fixed-inline").removeClass("ui-fixed-overlay");if(d<0||c.is(".ui-header-fixed")&&d!==0)b?c.css("top",0):c.css("top")!=="auto"&&parseFloat(c.css("top"))!==0&&c.animationComplete(function(){c.removeClass("out reverse").css("top",0)}).addClass("out reverse")})},startShowTimer:function(){a.mobile.fixedToolbars.clearShowTimer();var b=[].slice.call(arguments);h=setTimeout(function(){h=e;a.mobile.fixedToolbars.show.apply(null,b)},100)},clearShowTimer:function(){h&&
clearTimeout(h);h=e},toggle:function(b){b&&(g=b);return g==="overlay"?a.mobile.fixedToolbars.hide():a.mobile.fixedToolbars.show()},setTouchToggleEnabled:function(a){o=a}}}}();a(document).bind("pagecreate create",function(b){a(":jqmData(position='fixed')",b.target).length&&a(b.target).each(function(){if(!a.support.scrollTop||a.support.touchOverflow&&a.mobile.touchOverflowEnabled)return this;var b=a(this);b.jqmData("fullscreen")&&b.addClass("ui-page-fullscreen");b.find(".ui-header:jqmData(position='fixed')").addClass("ui-header-fixed ui-fixed-inline fade");
b.find(".ui-footer:jqmData(position='fixed')").addClass("ui-footer-fixed ui-fixed-inline fade")})})})(jQuery);
(function(a){a.mobile.touchOverflowEnabled=false;a.mobile.touchOverflowZoomEnabled=false;a(document).bind("pagecreate",function(e){a.support.touchOverflow&&a.mobile.touchOverflowEnabled&&(e=a(e.target),e.is(":jqmData(role='page')")&&e.each(function(){var b=a(this),d=b.find(":jqmData(role='header'), :jqmData(role='footer')").filter(":jqmData(position='fixed')"),e=b.jqmData("fullscreen"),c=d.length?b.find(".ui-content"):b;b.addClass("ui-mobile-touch-overflow");c.bind("scrollstop",function(){c.scrollTop()>
0&&window.scrollTo(0,a.mobile.defaultHomeScroll)});d.length&&(b.addClass("ui-native-fixed"),e&&(b.addClass("ui-native-fullscreen"),d.addClass("fade in"),a(document).bind("vclick",function(){d.removeClass("ui-native-bars-hidden").toggleClass("in out").animationComplete(function(){a(this).not(".in").addClass("ui-native-bars-hidden")})})))}))})})(jQuery);
(function(a,e){function b(){var b=a("meta[name='viewport']");b.length?b.attr("content",b.attr("content")+", user-scalable=no"):a("head").prepend("<meta>",{name:"viewport",content:"user-scalable=no"})}var d=a("html");a("head");var f=a(e);a(e.document).trigger("mobileinit");if(a.mobile.gradeA()){if(a.mobile.ajaxBlacklist)a.mobile.ajaxEnabled=false;d.addClass("ui-mobile ui-mobile-rendering");var c=a("<div class='ui-loader ui-body-a ui-corner-all'><span class='ui-icon ui-icon-loading spin'></span><h1></h1></div>");
a.extend(a.mobile,{showPageLoadingMsg:function(){if(a.mobile.loadingMessage){var b=a("."+a.mobile.activeBtnClass).first();c.find("h1").text(a.mobile.loadingMessage).end().appendTo(a.mobile.pageContainer).css({top:a.support.scrollTop&&f.scrollTop()+f.height()/2||b.length&&b.offset().top||100})}d.addClass("ui-loading")},hidePageLoadingMsg:function(){d.removeClass("ui-loading")},initializePage:function(){var b=a(":jqmData(role='page')");b.length||(b=a("body").wrapInner("<div data-"+a.mobile.ns+"role='page'></div>").children(0));
b.add(":jqmData(role='dialog')").each(function(){var b=a(this);b.jqmData("url")||b.attr("data-"+a.mobile.ns+"url",b.attr("id")||location.pathname+location.search)});a.mobile.firstPage=b.first();a.mobile.pageContainer=b.first().parent().addClass("ui-mobile-viewport");f.trigger("pagecontainercreate");a.mobile.showPageLoadingMsg();!a.mobile.hashListeningEnabled||!a.mobile.path.stripHash(location.hash)?a.mobile.changePage(a.mobile.firstPage,{transition:"none",reverse:true,changeHash:false,fromHashChange:true}):
f.trigger("hashchange",[true])}});a.support.touchOverflow&&a.mobile.touchOverflowEnabled&&!a.mobile.touchOverflowZoomEnabled&&b();a.mobile._registerInternalEvents();a(function(){e.scrollTo(0,1);a.mobile.defaultHomeScroll=!a.support.scrollTop||a(e).scrollTop()===1?0:1;a.mobile.autoInitializePage&&a.mobile.initializePage();f.load(a.mobile.silentScroll)})}})(jQuery,this);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10 -4
View File
@@ -27,7 +27,7 @@
{{pass}}
{{else:}}
{{qry=''}}
{{pass}}
{{pass}}
{{pass}}
<tr>
<th style="font-size: 1.75em;">
@@ -37,13 +37,19 @@
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}}
</table>
{{pass}}
{{pass}}
</table>
{{elif request.function=='select':}}
<h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
</h2>
{{if tb:}}
<h3>{{=T('Traceback')}}</h3>
<pre>
{{=tb}}
</pre>
{{pass}}
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
@@ -149,7 +155,7 @@
<h4>{{=T("RAM")}}</h4>
<p>{{=T.M("Number of entries: **%s**", ram['entries'])}}</p>
{{if ram['entries'] > 0:}}
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %{hit(hits)} and **%(misses)s** %%{miss(misses)})",
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
dict( ratio=ram['ratio'], hits=ram['hits'], misses=ram['misses']))}}
</p>
<p>
@@ -43,8 +43,7 @@ def file_create_form(location):
form=FORM(T("create file with filename:")," ",
INPUT(_type="text",_name="filename",requires=IS_NOT_EMPTY),
INPUT(_type="hidden",_name="location",_value=location),
INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)),
INPUT(_type="submit",_value=T("Create")),_action=URL('create_file'))
INPUT(_type="hidden",_name="sender",_value=URL('design',args=app)))
return form
def upload_plugin_form(app):
form=FORM(T("upload plugin file:")," ",
@@ -207,7 +206,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{pass}}
</ul>
<div data-role="fieldcontain">{{=file_create_form('%s/static/' % app)}}
{{=file_upload_form('%s/static/' % app)}}</div>
{{#=file_upload_form('%s/static/' % app)}}</div>
</div>
</div>
@@ -225,7 +224,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
</span>
{{pass}}
<div data-role="fieldcontain">{{=file_create_form('%s/modules/' % app)}}
{{=file_upload_form('%s/modules/' % app)}}</div>
{{#=file_upload_form('%s/modules/' % app)}}</div>
</div>
</div>
@@ -9,8 +9,9 @@
<div><input type="hidden" name="send" value="{{=send}}"/></div>
<table>
<tr><td>{{=T('Administrator Password:')}}</td><td><input type="password" name="password" id="password"/></td></tr>
<tr><td></td><td><input type="submit" name="login" value="{{=T('Login')}}"/></td></tr>
<tr><td></td><td><button name="login">{{=T('Login')}}</button></td></tr>
</table>
<input type="hidden" name="is_mobile" value="true"/>
</form>
</div>
{{else:}}
@@ -9,7 +9,7 @@
Remove this if you use the .htaccess -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>web2py mobile admin</title>
<title>Web2py Mobile Admin</title>
<!-- http://dev.w3.org/html5/markup/meta.name.html -->
<meta name="application-name" content="{{=request.application}}" />
@@ -24,16 +24,17 @@
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
-->
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references -->
<link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}">
<!-- For the less-enabled mobile browsers like Opera Mini -->
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.css')}}">
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.css')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
{{include 'web2py_ajax.html'}}
@@ -60,7 +61,7 @@ $(document).bind("mobileinit", function(){
});
</script>
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.js')}}"></script>
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.js')}}"></script>
<style>
.error { color: red; }
@@ -75,7 +76,7 @@ $(document).bind("mobileinit", function(){
<!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]-->
<div data-role="page">
<div data-role="header" data-fullscreen="true" data-position="fixed">
<div data-role="header">
<h1>web2py mobile admin/{{block sectionclass}}{{end}}</h1>
{{block header}}
{{if 'auth' in globals():}}
@@ -85,7 +86,7 @@ $(document).bind("mobileinit", function(){
<a rel="external" href="{{=URL('default','user',args='logout')}}" data-icon="logout" class="ui-btn-left">Logout</a>
{{pass}}
{{pass}}
<a rel="external" href="{{=URL('default','index')}}" data-icon="home" class="ui-btn-right">{{=T("Home")}}</a>
<a rel="external" href="{{=URL('default','index',vars=dict(is_mobile='true'))}}" data-icon="home" class="ui-btn-right">{{=T("Home")}}</a>
{{end}}
</div>
<div data-role="content">
@@ -100,7 +101,7 @@ $(document).bind("mobileinit", function(){
{{pass}}
{{include}}
</div>
<div data-role="footer" data-fullscreen="true" data-position="fixed" style="padding: 5px; text-align: center">
<div data-role="footer">
{{block footer}}
powered by web2py - @{{=request.now.year}}
{{end}}
@@ -4,16 +4,16 @@
<h2>{{=T("Installed applications")}}</h2>
<ul data-role="listview">
<ul data-role="listview" data-inset="true">
{{for a in apps:}}
<li>
{{if a==request.application:}}
<h3>{{=a}} ({{=T('currently running')}})</h3>
{{else:}}
<h3>{{=a}}</h3>
<h3>{{=T("Application")}} {{=a}}</h3>
{{if MULTI_USER_MODE and db.app(name=a):}}<p>created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}</p>{{pass}}
{{pass}}
<ul>
<ul data-role="listview" data-inset="true">
{{if a!=request.application:}}
{{=LI(A(T('Goto'),_rel="external",_href=URL(a,'default','index')))}}
{{if not os.path.exists('applications/%s/compiled' % a):}}
@@ -19,6 +19,10 @@
<script src="{{=cm}}/mode/css/css.js"></script>
<script src="{{=cm}}/mode/javascript/javascript.js"></script>
<script src="{{=cm}}/mode/htmlmixed/htmlmixed.js"></script>
<script src="{{=cm}}/lib/util/search.js"></script>
<script src="{{=cm}}/lib/util/searchcursor.js"></script>
<script src="{{=cm}}/lib/util/dialog.js"></script>
<link rel="stylesheet" href="{{=cm}}/lib/util/dialog.css">
<script src="{{=cm}}/emmet.min.js"></script>
<script language="Javascript" type="text/javascript" src="{{=URL('static','js/ajax_editor.js')}}"></script>
{{elif TEXT_EDITOR == 'ace':}}
@@ -246,6 +250,11 @@ window.onload = function() {
<ul>
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
{{=shortcut('Tab', T('Expand Abbreviation'))}}
</ul>
{{elif TEXT_EDITOR == 'codemirror':}}
@@ -253,6 +262,11 @@ window.onload = function() {
<ul>
{{=shortcut('Ctrl+S', T('Save via Ajax'))}}
{{=shortcut('Ctrl+F11', T('Toggle Fullscreen'))}}
{{=shortcut('Ctrl-F / Cmd-F', T('Start searching'))}}
{{=shortcut('Ctrl-G / Cmd-G', T('Find Next'))}}
{{=shortcut('Shift-Ctrl-G / Shift-Cmd-G', T('Find Previous'))}}
{{=shortcut('Shift-Ctrl-F / Cmd-Option-F', T('Replace'))}}
{{=shortcut('Shift-Ctrl-R / Shift-Cmd-Option-F', T('Replace All'))}}
</ul>
{{else:}}
<h3>{{=T("Key bindings")}}</h3>
@@ -76,6 +76,9 @@
<p>
{{=T("Running on %s", request.env.server_software)}}
</p>
{{if session.is_mobile=='auto':}}<p>
{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
{{pass}}
</div>
{{pass}}
<!-- MULTI_USER_INTERFACE -->
@@ -34,7 +34,7 @@
</style>
</head>
<body>
<iframe src="{{=URL('index')}}"></iframe>
<iframe src="{{=URL('default','index',vars=dict(is_mobile='true'))}}"></iframe>
<div id="about">
<h1><a href="http://web2py.com">web2py</a> plugin</h1>
<h2>for <a href="http://jquerymobile.com/">jQuery Mobile</a></h2>
@@ -24,17 +24,17 @@
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
-->
<!--meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;"-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Place favicon.ico and apple-touch-icon.png in the root of your domain and delete these references -->
<link rel="shortcut icon" href="{{=URL('static','favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','favicon.png')}}">
<!-- For the less-enabled mobile browsers like Opera Mini -->
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.css')}}">
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.css')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
{{include 'web2py_ajax.html'}}
@@ -61,7 +61,7 @@ $(document).bind("mobileinit", function(){
});
</script>
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.0.min.js')}}"></script>
<script src="{{=URL('static','plugin_jqmobile/jquery.mobile-1.2.0.min.js')}}"></script>
<style>
.error { color: red; }
@@ -76,7 +76,7 @@ $(document).bind("mobileinit", function(){
<!--[if (gt IE 9)|!(IE)]><!--> <body> <!--<![endif]-->
<div data-role="page">
<div data-role="header" data-fullscreen="true" data-position="fixed">
<div data-role="header">
<h1>{{=response.title}}</h1>
{{block header}}
{{if 'auth' in globals():}}
@@ -101,7 +101,7 @@ $(document).bind("mobileinit", function(){
{{pass}}
{{include}}
</div>
<div data-role="footer" data-fullscreen="true" data-position="fixed" style="padding: 5px; text-align: center">
<div data-role="footer">
{{block footer}}
powered by web2py - @{{=request.now.year}}
{{end}}
@@ -113,7 +113,6 @@ def query_by_table_type(tablename,db,request=request):
# ## list all databases and tables
# ###########################################################
def index():
return dict(databases=databases)
@@ -207,6 +206,7 @@ def select():
if match:
table = match.group('table')
try:
tb = None
nrows = db(query).count()
if form.vars.update_check and form.vars.update_fields:
db(query).update(**eval_in_global_env('dict(%s)'
@@ -221,6 +221,8 @@ def select():
else:
rows = db(query,ignore_common_filters=True).select(limitby=(start, stop))
except Exception, e:
import traceback
tb = traceback.format_exc()
(rows, nrows) = ([], 0)
response.flash = DIV(T('Invalid Query'),PRE(str(e)))
# begin handle upload csv
@@ -250,6 +252,7 @@ def select():
rows=rows,
query=request.vars.query,
formcsv = formcsv,
tb = tb,
)
@@ -38,7 +38,6 @@ def hello6():
def status():
""" page that shows internal status"""
response.view = 'generic.html'
return dict(toolbar=response.toolbar())
+10 -8
View File
@@ -10,7 +10,7 @@ h3 {font-size:2.00em}
h4 {font-size:1.50em}
h5 {font-size:1.25em}
h6 {font-size:1.12em}
th,label {font-weight:bold; white-space:nowrap}
th,label {font-weight:bold; white-space:nowrap;}
td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
@@ -69,7 +69,6 @@ fieldset {padding:16px; border-top:1px #DEDEDE solid}
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
/* fix ie problem with menu */
.ie-lte7 .topbar .container {z-index:2}
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
@@ -187,10 +186,7 @@ div.error {
.web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {
padding:2px 5px 2px 5px;
vertical-align:middle;
}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
@@ -299,10 +295,16 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; }
.ie9 #w2p_query_panel {padding-bottom:2px}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
}
/* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
+49 -14
View File
@@ -39,7 +39,7 @@ function web2py_ajax_init(target) {
function web2py_event_handlers() {
var doc = jQuery(document)
doc.on('click', '.flash', function(e){jQuery(this).fadeOut('slow'); e.preventDefault();});
doc.on('click', '.flash', function(e){var t=jQuery(this); if(t.css('top')=='0px') t.slideUp('slow'); else t.fadeOut(); e.preventDefault();});
doc.on('keyup', 'input.integer', function(){this.value=this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g,'').reverse();});
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
jQuery(function() {
var flash = jQuery('.flash');
flash.hide();
if(flash.html()) flash.slideDown();
if(flash.html()) flash.append('<span style="float:right;">&times;</span>').slideDown();
web2py_ajax_init(document);
web2py_event_handlers();
});
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
form.submit(function(e){
jQuery('.flash').hide().html('');
web2py_ajax_page('post',action,form.serialize(),target);
e.preventDefault();
e.preventDefault();
});
});
}
function web2py_trap_link(target) {
jQuery('#'+target+' a.w2p_trap').each(function(i){
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
}
function web2py_ajax_page(method, action, data, target) {
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
web2py_trap_link(target);
web2py_ajax_init('#'+target);
if(command)
eval(decodeURIComponent(command));
eval(decodeURIComponent(command));
if(flash)
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
}
});
}
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
}
} else {
// run once (no timeout specified)
element.reload_counter = Infinity;
element.reload_counter = Infinity;
web2py_ajax_page('get', action, null, target);
} }); }
function web2py_comet(url,onmessage,onopen,onclose) {
function web2py_websocket(url,onmessage,onopen,onclose) {
if ("WebSocket" in window) {
var ws = new WebSocket(url);
ws.onopen = onopen?onopen:(function(){});
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
} else return false; // not supported
}
function web2py_calc_entropy(mystring) {
//calculate a simple entropy for a given string
var csets = new Array(
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
'0123456789abcdefghijklmnopqrstuvwxyz');
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) { // classify this character
var c = mystringlist[i], inset=5;
for(var j = 0; j<csets.length; j++)
if (csets[j].indexOf(c) != -1) {inset = j; break;}
//calculate effect of character on alphabet size
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
else if (!(c in other)) {score += 1;other[c] = 1;}
if (inset != lastset) {score += 1;lastset = inset;}
}
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
return Math.round(entropy*100)/100
}
function web2py_validate_entropy(myfield, req_entropy) {
var validator = function () {
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
if(v<=0.5) {r=1.0; g=2.0*v;}
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
var color = '#'+rs(r)+rs(g)+rs(b);
myfield.css('background-color',color);
entropy_callback = myfield.data('entropy_callback');
if(entropy_callback) entropy_callback(v);
}
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}
+10 -4
View File
@@ -27,7 +27,7 @@
{{pass}}
{{else:}}
{{qry=''}}
{{pass}}
{{pass}}
{{pass}}
<tr>
<th style="font-size: 1.75em;">
@@ -37,13 +37,19 @@
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}}
</table>
{{pass}}
{{pass}}
</table>
{{elif request.function=='select':}}
<h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
</h2>
{{if tb:}}
<h3>{{=T('Traceback')}}</h3>
<pre>
{{=tb}}
</pre>
{{pass}}
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
@@ -149,7 +155,7 @@
<h4>{{=T("RAM")}}</h4>
<p>{{=T.M("Number of entries: **%s**", ram['entries'])}}</p>
{{if ram['entries'] > 0:}}
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %{hit(hits)} and **%(misses)s** %%{miss(misses)})",
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
dict( ratio=ram['ratio'], hits=ram['hits'], misses=ram['misses']))}}
</p>
<p>
@@ -80,8 +80,7 @@ Current version: {{="%s.%s.%s (%s) %s" % request.env.web2py_version}}</p>
<br/>
<a class="button" href="{{=URL('download')}}" style="width:90%">DOWNLOAD NOW</a><br/>
<a class="button" href="http://web2py.com/demo_admin" style="width:90%">ONLINE DEMO</a><br/>
<a class="button" href="http://web2py.com/poweredby" style="width:90%">SITES POWERED BY WEB2PY</a>
<br/>
<object width="234" height="60"><param name="movie" value="http://widget.chipin.com/widget/id/dcd384d58839aa4d"></param><param name="allowScriptAccess" value="always"></param><param name="wmode" value="transparent"></param><param name="color_scheme" value="gray"></param><embed src="http://widget.chipin.com/widget/id/dcd384d58839aa4d" flashVars="color_scheme=gray" type="application/x-shockwave-flash" allowScriptAccess="always" wmode="transparent" width="234" height="60"></embed></object>
<a class="button" href="http://web2py.com/poweredby" style="width:90%">SITES POWERED BY WEB2PY</a><br/>
<a class="button" href="http://www.chipin.com/contribute/id/dcd384d58839aa4d" style="width:90%">SUPPORT/DONATE</a></br>
</div>
{{end}}
@@ -70,6 +70,7 @@
</li><li>Ian Reinhart Geiser (html helpers)
</li><li>Ionel Anton (Romanian translation)
</li><li>Jan Beilicke (markmin)
</li><li>Jeremy Dillworth
</li><li>Jonathan Benn (is_url validator and tests)
</li><li>Jonathan Lundell (multiple contributions)
</li><li>Josh Goldfoot (xaml/html sanitizer)
@@ -156,6 +157,7 @@
<li><a href="http://www.danga.com/memcached/">memcache</a> developed by Evan Martin</li>
<li><a href="http://jquery.com/">jQuery</a> developed by John Resig</li>
<li>A syntax highlighter inspired by the code of <a href="http://www.petersblog.org/node/763">Peter Wilkinson</a></li>
<li><a href="https://github.com/jtauber/pyuca">pyUCA</a> developed by <a href="http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/">James Tauber</a></li>
</ul>
+4 -1
View File
@@ -113,7 +113,6 @@ def query_by_table_type(tablename,db,request=request):
# ## list all databases and tables
# ###########################################################
def index():
return dict(databases=databases)
@@ -207,6 +206,7 @@ def select():
if match:
table = match.group('table')
try:
tb = None
nrows = db(query).count()
if form.vars.update_check and form.vars.update_fields:
db(query).update(**eval_in_global_env('dict(%s)'
@@ -221,6 +221,8 @@ def select():
else:
rows = db(query,ignore_common_filters=True).select(limitby=(start, stop))
except Exception, e:
import traceback
tb = traceback.format_exc()
(rows, nrows) = ([], 0)
response.flash = DIV(T('Invalid Query'),PRE(str(e)))
# begin handle upload csv
@@ -250,6 +252,7 @@ def select():
rows=rows,
query=request.vars.query,
formcsv = formcsv,
tb = tb,
)
@@ -5,6 +5,7 @@
'book': ['books'],
'is': ['are'],
'man': ['men'],
'miss': ['misses'],
'person': ['people'],
'quark': ['quarks'],
'shop': ['shops'],
+1 -1
View File
@@ -11,7 +11,7 @@
if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB
db = DAL('sqlite://storage.sqlite')
db = DAL('sqlite://storage.sqlite')
else:
## connect to Google BigTable (optional 'google:datastore://namespace')
db = DAL('google:datastore')
+2 -2
View File
@@ -33,9 +33,9 @@ def _():
# shortcuts
app = request.application
ctr = request.controller
# useful links to internal and external resources
# useful links to internal and external resources
response.menu+=[
(SPAN('web2py',_style='color:yellow'),False, 'http://web2py.com', [
(SPAN('web2py',_class='highlighted'),False, 'http://web2py.com', [
(T('My Sites'),False,URL('admin','default','site')),
(T('This App'),False,URL('admin','default','design/%s' % app), [
(T('Controller'),False,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10 -5
View File
@@ -61,7 +61,7 @@ input[type=text],input[type=password],select{width:300px; margin-right:5px}
border-top:1px #DEDEDE solid;
}
.header {
// background:<fill here for header image>;
/* background:<fill here for header image>; */
}
@@ -69,7 +69,6 @@ fieldset {padding:16px; border-top:1px #DEDEDE solid}
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
/* fix ie problem with menu */
.ie-lte7 .topbar .container {z-index:2}
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
@@ -183,7 +182,7 @@ div.error {
* will look better with the declarations below
* if needed to remove base.css consider keeping these following lines in some css file.
*/
// .web2py_table {border:1px solid #ccc}
/* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
@@ -296,10 +295,16 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; }
.ie9 #w2p_query_panel {padding-bottom:2px}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
}
/* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
@@ -1,20 +1,19 @@
#navbar .auth_navbar, #navbar .auth_navbar a {color:inherit;}
/*=============================================================
CUSTOM RULES
==============================================================*/
div.flash.flash-center {
left: 25%;
right: 25%;
}
div.flash.flash-top, div.flash.flash-top:hover {
position: relative;
display: block;
body{height:auto;} /* to avoid vertical scroll bar */
div.flash.flash-center{left:25%;right:25%;}
div.flash.flash-top,div.flash.flash-top:hover{
position:relative;
display:block;
margin:0;
padding:1em;
top:0;
left:0;
width:100%;
text-align:center;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
color:#865100;
background:#feea9a;
border:1px solid;
@@ -24,154 +23,209 @@ div.flash.flash-top, div.flash.flash-top:hover {
border-radius:0;
opacity:1;
}
/* bootstrap dropdown */
.dropdown-menu ul {
left: 100%;
position: absolute;
top: 0;
visibility: hidden;
margin-top: -1px;
}
.dropdown-menu li:hover ul {
visibility: visible;
}
#header {
margin-top: 60px;
}
#header{margin-top:60px;}
.mastheader h1 {
margin-bottom: 9px;
font-size: 81px;
font-weight: bold;
letter-spacing: -1px;
line-height: 1;
margin-bottom:9px;
font-size:81px;
font-weight:bold;
letter-spacing:-1px;
line-height:1;
font-size:54px;
}
.mastheader h1 {
font-size: 54px;
}
.mastheader small {
font-size: 20px;
font-weight: 300;
font-size:20px;
font-weight:300;
}
.navbar .dropdown-menu ul:before {
border-bottom: 7px solid transparent;
border-left: none;
border-right: 7px solid rgba(0, 0, 0, 0.2);
border-top: 7px solid transparent;
left: -7px;
top: 5px;
}
.navbar .dropdown-menu ul:after {
border-top: 6px solid transparent;
border-left: none;
border-right: 6px solid #fff;
border-bottom: 6px solid transparent;
left: 10px;
top: 6px;
left: -6px;
}
.dropdown-menu span{
/* auth navbar - primitive style */
.auth_navbar,.auth_navbar a{color:inherit;}
.ie-lte7 .auth_navbar,.auth_navbar a{color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
.auth_navbar a{white-space:nowrap;} /* to avoid the nav split on more lines */
.auth_navbar a:hover{color:white;text-decoration:none;}
ul#navbar>.auth_navbar{
display:inline-block;
padding:5px;
}
.chevron-right {
border-left: 4px solid #000;
border-right: 4px solid transparent;
border-bottom: 4px solid transparent;
border-top: 4px solid transparent;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-right:-13px;
margin-top: 7px;
float:right;
}
.open > .dropdown-menu ul { /* fix menu issue when BS2.0.4 is applied */
display: block;
}
.ie-lte7 #navbar .auth_navbar, #navbar .auth_navbar a {color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
#navbar .auth_navbar a:hover {color:white;text-decoration:none;} /* this overwrite bootswatch */
ul#navbar{padding:0;} /*reset ul padding */
ul#navbar>.auth_navbar{display:inline-block;padding:5px;} /* set padding of span.auth_navbar inside ul*/
body {
height:auto; /*to avoid vertical scroll bar*/
}
h1,h2,h3,h4,h5,h6 {font-family: inherit;}
li {margin-bottom: 0;} /*bootswatch*/
.auth_navbar, .navbar .btn-group { padding:0; }
[class^="icon-"],[class*=" icon-"]{background-image:url("../images/glyphicons-halflings.png")} /* right folder for bootstrap black images/icons */
.icon-white{background-image:url("../images/glyphicons-halflings-white.png");} /* right folder for bootstrap white images/icons */
.navbar-inner{
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
position:relative;
}
@media only screen and (max-width: 979px) {
body {
padding-top: 0px;
}
#navbar{bottom:-10px;left:4px;}
div.flash {
right: 5px;
}
.dropdown-menu ul {
visibility: visible;
}
}
div.error_wrapper {
margin-bottom: 9px;
}
div.error {
/* form errors message box customization */
div.error_wrapper{margin-bottom:9px;}
div.error_wrapper .error{
border-radius: 4px;
-o-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
/* below rules are only for formstyle = bootstrap
trying to make errors look like bootstrap ones */
div.controls .error_wrapper{
display:inline-block;
margin-bottom:0;
vertical-align:middle;
}
div.controls .error{
min-width:5px;
background:inherit;
color:#B94A48;
border:none;
padding:0;
margin:0;
//display:inline; /* uncommenting this, the animation effect is lost */
}
div.controls .inline-help{color:#3A87AD;}
div.controls .error_wrapper+.inline-help{margin-left:-99999px;}
/* beautify brand */
.navbar-inverse .brand{color:#c6cecc;}
.navbar-inverse .brand b{display:inline-block;margin-top:-1px;}
.navbar-inverse .brand b>span{font-size:22px;color:white}
.navbar-inverse .brand:hover b>span{color:white}
/* beautify web2py link in navbar */
span.highlighted{color:#d8d800;}
.open span.highlighted{color:#ffff00;}
input[type="button"], input[type="submit"] {
// to be fixed
/*=============================================================
OVERRIDING WEB2PY.CSS RULES
==============================================================*/
/* reset to default */
a{white-space:normal;}
li{margin-bottom:0;}
textarea,button{display:block;}
/*reset ul padding */
ul#navbar{padding:0;}
/* label aligned to related input */
td.w2p_fl,td.w2p_fc {padding:0;}
#web2py_user_form td{vertical-align:middle;}
/*=============================================================
OVERRIDING BOOTSTRAP.CSS RULES
==============================================================*/
/* because web2py handles this via js */
.hidden{visibility:visible;}
/* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{
background-image:url("../images/glyphicons-halflings.png")
}
/* right folder for bootstrap white images/icons */
.icon-white,
.nav-tabs > .active > a > [class^="icon-"],
.nav-tabs > .active > a > [class*=" icon-"],
.nav-pills > .active > a > [class^="icon-"],
.nav-pills > .active > a > [class*=" icon-"],
.nav-list > .active > a > [class^="icon-"],
.nav-list > .active > a > [class*=" icon-"],
.navbar-inverse .nav > .active > a > [class^="icon-"],
.navbar-inverse .nav > .active > a > [class*=" icon-"],
.dropdown-menu > li > a:hover > [class^="icon-"],
.dropdown-menu > li > a:hover > [class*=" icon-"],
.dropdown-menu > .active > a > [class^="icon-"],
.dropdown-menu > .active > a > [class*=" icon-"] {
background-image:url("../images/glyphicons-halflings-white.png");
}
/* bootstrap has a label as input's wrapper while web2py has a div */
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
/* bootstrap has button instead of input */
input[type="button"], input[type="submit"]{margin-right:8px;}
/*=============================================================
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
==============================================================*/
/*when formstyle=table3cols*/
tr#auth_user_remember__row>td.w2p_fw>div{padding-bottom:8px;}
td.w2p_fw div>label{vertical-align:middle;}
td.w2p_fc {padding-bottom:5px;}
/*when formstyle=divs*/
div#auth_user_remember__row{margin-top:4px;}
div#auth_user_remember__row>.w2p_fl{display:none;}
div#auth_user_remember__row>.w2p_fw{min-height:39px;}
div.w2p_fw,div.w2p_fc{
display:inline-block;
vertical-align:middle;
margin-bottom:0;
}
div.w2p_fc{
padding-left:5px;
margin-top:-8px;
}
/*when formstyle=ul*/
form>ul{
list-style:none;
margin:0;
}
li#auth_user_remember__row{margin-top:4px;}
li#auth_user_remember__row>.w2p_fl{display:none;}
li#auth_user_remember__row>.w2p_fw{min-height:39px;}
/*when formstyle=bootstrap*/
#auth_user_remember__row label.checkbox{display:block;}
span.inline-help{display:inline-block;}
input[type="text"].input-xlarge,input[type="password"].input-xlarge{width:270px;}
/*when recaptcha is used*/
#recaptcha{min-height:30px;display:inline-block;margin-bottom:0;line-height:30px;vertical-align:middle;}
td>#recaptcha{margin-bottom:6px;}
div>#recaptcha{margin-bottom:9px;}
div.control-group.error{
width:auto;
background:transparent;
border:0;
color:inherit;
padding:0;
background-repeat:repeat;
}
@media only screen and (max-width: 479px) {
body {
padding-left: 10px;
padding-right: 10px;
/*=============================================================
OTHER RULES
==============================================================*/
.navbar-inner{
position:relative; /*unnecessary ??*/
}
/* Massimo Di Pierro fixed alignment in forms with list:string */
form table tr{margin-bottom:9px;}
td.w2p_fw ul{margin-left:0px;}
/* web2py_console in grid and smartgrid */
.hidden{visibility:visible;}
.web2py_console input{
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
.web2py_console input[type="submit"],
.web2py_console input[type="button"],
.web2py_console button{
padding-top:4px;
padding-bottom:4px;
margin:3px 0 0 2px;
}
.web2py_console a,
.web2py_console select,
.web2py_console input
{
margin:3px 0 0 2px;
}
.web2py_grid form table{width:auto;}
/* auth_user_remember checkbox extrapadding in IE fix */
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media only screen and (max-width:979px){
body{padding-top:0px;}
#navbar{bottom:-10px;left:4px;}
div.flash{right:5px;}
.dropdown-menu ul{visibility:visible;}
}
@media only screen and (max-width:479px){
body{
padding-left:10px;
padding-right:10px;
}
.navbar-fixed-top, .navbar-fixed-bottom {
margin-left: -10px;
margin-right: -10px;
.navbar-fixed-top,.navbar-fixed-bottom {
margin-left:-10px;
margin-right:-10px;
}
input[type="text"], input[type="password"], select {
width: 95%;
input[type="text"],input[type="password"],select{
width:95%;
}
}
}
@@ -1,3 +1,23 @@
/*=============================================================
BOOTSTRAP DROPDOWN MENU
==============================================================*/
.dropdown-menu ul{
left:100%;
position:absolute;
top:0;
visibility:hidden;
margin-top:-1px;
}
.dropdown-menu li:hover ul{visibility:visible;}
.navbar .dropdown-menu ul:before{
border-bottom:7px solid transparent;
border-left:none;
border-right:7px solid rgba(0, 0, 0, 0.2);
border-top:7px solid transparent;
left:-7px;
top:5px;
}
.nav > li.dropdown > a:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
@@ -15,8 +35,7 @@
border-bottom-color: #FFFFFF;
border-top-color: #FFFFFF;
}
.dropdown-menu span{display:inline-block;}
ul.dropdown-menu li.dropdown > a:after {
border-left: 4px solid #000;
border-right: 4px solid transparent;
@@ -35,4 +54,69 @@ ul.dropdown-menu li.dropdown > a:after {
ul.nav li.dropdown:hover ul.dropdown-menu {
display: block;
}
}
.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */
/*=============================================================
BOOTSTRAP SUBMIT BUTTON
==============================================================*/
input[type='submit']:not(.btn) {
display: inline-block;
padding: 4px 14px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
color: #333;
text-align: center;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
vertical-align: middle;
cursor: pointer;
background-color: whiteSmoke;
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
background-image: -o-linear-gradient(top,white,#E6E6E6);
background-image: linear-gradient(to bottom,white,#E6E6E6);
background-image: -moz-linear-gradient(top,white,#E6E6E6);
background-repeat: repeat-x;
border: 1px solid #BBB;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
border-bottom-color: #A2A2A2;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
}
input[type='submit']:not(.btn):hover {
color: #333;
text-decoration: none;
background-color: #E6E6E6;
background-position: 0 -15px;
-webkit-transition: background-position .1s linear;
-moz-transition: background-position .1s linear;
-o-transition: background-position .1s linear;
transition: background-position .1s linear;
}
input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active {
background-color: #E6E6E6;
background-color: #D9D9D9 9;
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
}
/*=============================================================
OTHER
==============================================================*/
.ie-lte8 .navbar-fixed-top {position:static;}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -21,8 +21,8 @@ jQuery(function(){
var title = escape(jQuery('title').text());
var twit = 'http://twitter.com/home?status='+title+'%20'+url;
var facebook = 'http://www.facebook.com/sharer.php?u='+url;
var buzz = 'http://www.google.com/reader/link?url='+url+'&amp;title='+title+'&amp;srcURL='+host;
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+buzz+'" id="buzz" title="Share on Buzz"><img src="'+path+'/google-buzz.png" alt="Share on Buzz" width="32" height="32" /></a></div></div>';
var gplus = 'https://plus.google.com/share?url='+url;
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+gplus+'" id="gplus" title="Share on Google Plus"><img src="'+path+'/gplus-32.png" alt="Share on Google Plus" width="32" height="32" /></a></div></div>';
// Add the share tool bar.
jQuery('body').append(tbar);
var st = jQuery('#socialdrawer');
+48 -13
View File
@@ -55,7 +55,7 @@ function web2py_event_handlers() {
jQuery(function() {
var flash = jQuery('.flash');
flash.hide();
if(flash.html()) flash.append('<span style="float:right;">&times;<span>').slideDown();
if(flash.html()) flash.append('<span style="float:right;">&times;</span>').slideDown();
web2py_ajax_init(document);
web2py_event_handlers();
});
@@ -67,20 +67,20 @@ function web2py_trap_form(action,target) {
form.submit(function(e){
jQuery('.flash').hide().html('');
web2py_ajax_page('post',action,form.serialize(),target);
e.preventDefault();
e.preventDefault();
});
});
}
function web2py_trap_link(target) {
jQuery('#'+target+' a.w2p_trap').each(function(i){
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
var link=jQuery(this);
link.click(function(e) {
jQuery('.flash').hide().html('');
web2py_ajax_page('get',link.attr('href'),[],target);
e.preventDefault();
});
});
}
function web2py_ajax_page(method, action, data, target) {
@@ -101,9 +101,9 @@ function web2py_ajax_page(method, action, data, target) {
web2py_trap_link(target);
web2py_ajax_init('#'+target);
if(command)
eval(decodeURIComponent(command));
eval(decodeURIComponent(command));
if(flash)
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
jQuery('.flash').html(decodeURIComponent(flash)).slideDown();
}
});
}
@@ -151,11 +151,11 @@ function web2py_component(action, target, timeout, times){
}
} else {
// run once (no timeout specified)
element.reload_counter = Infinity;
element.reload_counter = Infinity;
web2py_ajax_page('get', action, null, target);
} }); }
function web2py_comet(url,onmessage,onopen,onclose) {
function web2py_websocket(url,onmessage,onopen,onclose) {
if ("WebSocket" in window) {
var ws = new WebSocket(url);
ws.onopen = onopen?onopen:(function(){});
@@ -165,3 +165,38 @@ function web2py_comet(url,onmessage,onopen,onclose) {
} else return false; // not supported
}
function web2py_calc_entropy(mystring) {
//calculate a simple entropy for a given string
var csets = new Array(
'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
'0123456789abcdefghijklmnopqrstuvwxyz');
var score = 0, other = {}, seen = {}, lastset = null, mystringlist = mystring.split('');
for (var i=0;i<mystringlist.length;i++) { // classify this character
var c = mystringlist[i], inset=5;
for(var j = 0; j<csets.length; j++)
if (csets[j].indexOf(c) != -1) {inset = j; break;}
//calculate effect of character on alphabet size
if(!(inset in seen)) {seen[inset] = 1;score += csets[inset].length;}
else if (!(c in other)) {score += 1;other[c] = 1;}
if (inset != lastset) {score += 1;lastset = inset;}
}
var entropy = mystring.length*Math.log(score)/0.6931471805599453;
return Math.round(entropy*100)/100
}
function web2py_validate_entropy(myfield, req_entropy) {
var validator = function () {
var v = (web2py_calc_entropy(myfield.val())||0)/req_entropy;
var r=0,g=0,b=0,rs=function(x){return Math.round(x*15).toString(16)};
if(v<=0.5) {r=1.0; g=2.0*v;}
else {r=(1.0-2.0*(Math.max(v,0)-0.5)); g=1.0;}
var color = '#'+rs(r)+rs(g)+rs(b);
myfield.css('background-color',color);
entropy_callback = myfield.data('entropy_callback');
if(entropy_callback) entropy_callback(v);
}
if(!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
}
@@ -0,0 +1,28 @@
// this code improves bootstrap menus and adds dropdown support
jQuery(function(){
jQuery('.nav>li>a').each(function(){
if(jQuery(this).parent().find('ul').length)
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
});
jQuery('.nav li li').each(function(){
if(jQuery(this).find('ul').length)
jQuery(this).addClass('dropdown-submenu');
});
function hoverMenu(){
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
if (wid>=980){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
};
}
hoverMenu(); // first page load
jQuery(window).resize(hoverMenu); // on resize event
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
});
+10 -4
View File
@@ -27,7 +27,7 @@
{{pass}}
{{else:}}
{{qry=''}}
{{pass}}
{{pass}}
{{pass}}
<tr>
<th style="font-size: 1.75em;">
@@ -37,13 +37,19 @@
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
</td>
</tr>
{{pass}}
</table>
{{pass}}
{{pass}}
</table>
{{elif request.function=='select':}}
<h2>{{=XML(str(T("Database %s select"))%A(request.args[0],_href=URL('index'))) }}
</h2>
{{if tb:}}
<h3>{{=T('Traceback')}}</h3>
<pre>
{{=tb}}
</pre>
{{pass}}
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
@@ -149,7 +155,7 @@
<h4>{{=T("RAM")}}</h4>
<p>{{=T.M("Number of entries: **%s**", ram['entries'])}}</p>
{{if ram['entries'] > 0:}}
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %{hit(hits)} and **%(misses)s** %%{miss(misses)})",
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
dict( ratio=ram['ratio'], hits=ram['hits'], misses=ram['misses']))}}
</p>
<p>
+9 -5
View File
@@ -4,16 +4,20 @@
{{
if request.args(0)=='login':
if not 'register' in auth.settings.actions_disabled:
form.add_button(T('Register'),URL(args='register'))
form.add_button(T('Register'),URL(args='register'),_class='btn')
pass
if not 'request_reset_password' in auth.settings.actions_disabled:
form.add_button(T('Lost Password'),URL(args='request_reset_password'))
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn')
pass
pass
=form
}}
</div>
<script language="javascript"><!--
jQuery("#web2py_user_form input:visible:enabled:first").focus();
//--></script>
jQuery("#web2py_user_form input:visible:enabled:first").focus();
{{if request.args(0)=='register':}}
web2py_validate_entropy(jQuery('#auth_user_password'),100);
{{elif request.args(0)=='change_password':}}
web2py_validate_entropy(jQuery('#no_table_new_password'),100);
{{pass}}
//--></script>
+1 -1
View File
@@ -1 +1 @@
{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
<?xml version="1.0" encoding="ISO-8859-1"?>{{from gluon.serializers import xml}}{{=XML(xml(response._vars,quote=False))}}
+7 -29
View File
@@ -39,10 +39,10 @@
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
<!-- include stylesheets -->
{{
{{
response.files.append(URL('static','css/web2py.css'))
response.files.append(URL('static','css/bootstrap.min.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
response.files.append(URL('static','css/web2py.css'))
response.files.append(URL('static','css/web2py_bootstrap.css'))
}}
@@ -66,17 +66,17 @@
<body>
<!-- Navbar ================================================== -->
<div class="navbar navbar-fixed-top">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="flash">{{=response.flash or ''}}</div>
<div class="navbar-inner">
<div class="container">
<!-- the next tag is necessary for bootstrap menus, do not remove -->
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#">web2py&trade;&nbsp;</a>
</button>
<a class="brand" href="http://www.web2py.com/"><b>web<span>2</span>py</b>&trade;&nbsp;</a>
<ul id="navbar" class="nav pull-right">{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}</ul>
<div class="nav-collapse">
{{is_mobile=request.user_agent().is_mobile}}
@@ -146,30 +146,8 @@
<!-- The javascript =============================================
(Placed at the end of the document so the pages load faster) -->
<script>
// this code improves bootstrap menus and adds dropdown support
jQuery(function(){
jQuery('.nav>li>a').each(function(){
if(jQuery(this).parent().find('ul').length)
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
});
jQuery('.nav li li').each(function(){
if(jQuery(this).find('ul').length)
jQuery(this).children('a').contents().before('<i class="chevron-right"></i>');
});
if(jQuery(document).width()>=980) {
jQuery('ul.nav li.dropdown').hover(function() {
jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeIn();
}, function() {
jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeOut();
});
}
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn').css({'margin-right':'2px','margin-bottom':'2px'});
});
</script>
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
<!--[if lt IE 7 ]>
<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>
-2
View File
@@ -83,9 +83,7 @@ def wsgiapp(env, res):
if global_settings.web2py_runtime == 'gae:development':
gluon.admin.create_missing_folders()
from gluon.custom_import import custom_import_install
web2py_path = global_settings.applications_parent # backward compatibility
custom_import_install(web2py_path)
return gluon.main.wsgibase(env, res)
+1 -1
View File
@@ -60,7 +60,7 @@ def app_pack(app, request, raise_ex=False):
"""
try:
app_cleanup(app, request)
filename = apath('../deposit/%s.w2p' % app, request)
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
w2p_pack(filename, apath(app, request))
return filename
except Exception, e:
+105 -111
View File
@@ -11,7 +11,7 @@ Basic caching classes and methods
- Cache - The generic caching object interfacing with the others
- CacheInRam - providing caching in ram
- CacheInDisk - provides caches on disk
- CacheOnDisk - provides caches on disk
Memcache is also available via a different module (see gluon.contrib.memcache)
@@ -46,6 +46,19 @@ class CacheAbstract(object):
Main function is now to provide referenced api documentation.
Use CacheInRam or CacheOnDisk instead which are derived from this class.
Attentions, Michele says:
There are signatures inside gdbm files that are used directly
by the python gdbm adapter that often are lagging behind in the
detection code in python part.
On every occasion that a gdbm store is probed by the python adapter,
the probe fails, because gdbm file version is newer.
Using gdbm directly from C would work, because there is backward
compatibility, but not from python!
The .shelve file is discarded and a new one created (with new
signature) and it works until it is probed again...
The possible consequences are memory leaks and broken sessions.
"""
cache_stats_name = 'web2py_cache_statistics'
@@ -130,22 +143,27 @@ class CacheInRam(CacheAbstract):
meta_storage = {}
def __init__(self, request=None):
self.locker.acquire()
self.initialized = False
self.request = request
def initialize(self):
if self.initialized: return
else: self.initialized = True
self.locker.acquire()
request = self.request
if request:
app = request.application
else:
app = ''
if not app in self.meta_storage:
self.storage = self.meta_storage[app] = {CacheAbstract.cache_stats_name: {
'hit_total': 0,
'misses': 0,
}}
self.storage = self.meta_storage[app] = {
CacheAbstract.cache_stats_name: {'hit_total': 0, 'misses': 0}}
else:
self.storage = self.meta_storage[app]
self.locker.release()
def clear(self, regex=None):
self.initialize()
self.locker.acquire()
storage = self.storage
if regex is None:
@@ -154,10 +172,7 @@ class CacheInRam(CacheAbstract):
self._clear(storage, regex)
if not CacheAbstract.cache_stats_name in storage.keys():
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0,
'misses': 0,
}
storage[CacheAbstract.cache_stats_name] = {'hit_total': 0,'misses': 0}
self.locker.release()
@@ -172,6 +187,7 @@ class CacheInRam(CacheAbstract):
3) would work unless we deepcopy no storage and retrival which would make things slow.
Anyway. You can deepcopy explicitly in the function generating the value to be cached.
"""
self.initialize()
dt = time_expire
now = time.time()
@@ -200,6 +216,7 @@ class CacheInRam(CacheAbstract):
return value
def increment(self, key, value=1):
self.initialize()
self.locker.acquire()
try:
if key in self.storage:
@@ -226,58 +243,65 @@ class CacheOnDisk(CacheAbstract):
Values stored in disk cache must be pickable.
"""
speedup_checks = set()
def _close_shelve_and_unlock(self):
try:
if self.storage:
self.storage.close()
finally:
if self.locker and self.locked:
portalocker.unlock(self.locker)
self.locker.close()
self.locked = False
def _open_shelf_with_lock(self):
def _open_shelve_and_lock(self):
"""Open and return a shelf object, obtaining an exclusive lock
on self.locker first. Replaces the close method of the
returned shelf instance with one that releases the lock upon
closing."""
def _close(self):
try:
shelve.Shelf.close(self)
finally:
portalocker.unlock(self.locker)
self.locker.close()
storage, locker, locker_locked = None, None, False
storage = None
locker = None
locked = False
try:
locker = open(self.locker_name, 'a')
locker = locker = open(self.locker_name, 'a')
portalocker.lock(locker, portalocker.LOCK_EX)
locker_locked = True
storage = shelve.open(self.shelve_name)
storage.close = _close.__get__(storage, shelve.Shelf)
storage.locker = locker
except Exception:
logger.error('corrupted cache file %s, will try to delete and recreate it!' % (self.shelve_name))
locked = True
try:
storage = shelve.open(self.shelve_name)
except:
logger.error('corrupted cache file %s, will try rebuild it' \
% (self.shelve_name))
storage = None
if not storage and os.path.exists(self.shelve_name):
os.unlink(self.shelve_name)
storage = shelve.open(self.shelve_name)
if not CacheAbstract.cache_stats_name in storage.keys():
storage[CacheAbstract.cache_stats_name] = {'hit_total':0, 'misses': 0}
storage.sync()
except Exception, e:
if storage:
storage.close()
storage = None
try:
os.unlink(self.shelve_name)
storage = shelve.open(self.shelve_name)
storage.close = _close.__get__(storage, shelve.Shelf)
storage.locker = locker
if not CacheAbstract.cache_stats_name in storage.keys():
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0,
'misses': 0,
}
storage.sync()
except (IOError, OSError):
logger.warn('unable to delete and recreate cache file %s' % self.shelve_name)
if storage:
storage.close()
storage = None
if locker_locked:
portalocker.unlock(locker)
if locker:
locker.close()
if locked:
portalocker.unlock(locker)
locker.close()
locked = False
raise RuntimeError, 'unable to create/re-create cache file %s' % self.shelve_name
self.locker = locker
self.locked = locked
self.storage = storage
return storage
def __init__(self, request, folder=None):
def __init__(self, request=None, folder=None):
self.initialized = False
self.request = request
self.folder = folder
def initialize(self):
if self.initialized: return
else: self.initialized = True
folder = self.folder
request = self.request
# Lets test if the cache folder exists, if not
# we are going to create it
@@ -291,93 +315,54 @@ class CacheOnDisk(CacheAbstract):
self.locker_name = os.path.join(folder,'cache.lock')
self.shelve_name = os.path.join(folder,'cache.shelve')
speedup_key = (folder,CacheAbstract.cache_stats_name)
if not speedup_key in self.speedup_checks or \
not os.path.exists(self.shelve_name):
try:
storage = self._open_shelf_with_lock()
try:
if not CacheAbstract.cache_stats_name in storage:
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0,
'misses': 0,
}
storage.sync()
finally:
storage.close()
self.speedup_checks.add(speedup_key)
except ImportError:
pass # no module _bsddb, ignoring exception now so it makes a ticket only if used
def clear(self, regex=None):
storage = self._open_shelf_with_lock()
self.initialize()
storage = self._open_shelve_and_lock()
try:
if regex is None:
storage.clear()
else:
self._clear(storage, regex)
if not CacheAbstract.cache_stats_name in storage.keys():
storage[CacheAbstract.cache_stats_name] = {
'hit_total': 0,
'misses': 0,
}
storage.sync()
finally:
storage.close()
self._close_shelve_and_unlock()
def __call__(self, key, f,
time_expire = DEFAULT_TIME_EXPIRE):
self.initialize()
dt = time_expire
storage = self._open_shelf_with_lock()
storage = self._open_shelve_and_lock()
try:
item = storage.get(key, None)
storage[CacheAbstract.cache_stats_name]['hit_total'] += 1
if item and f is None:
del storage[key]
storage[CacheAbstract.cache_stats_name] = {
'hit_total': storage[CacheAbstract.cache_stats_name]['hit_total'] + 1,
'misses': storage[CacheAbstract.cache_stats_name]['misses']
}
storage.sync()
storage.sync()
now = time.time()
if f is None:
value = None
elif item and (dt is None or item[0] > now - dt):
value = item[1]
else:
value = f()
storage[key] = (now, value)
storage[CacheAbstract.cache_stats_name]['misses']+=1
storage.sync()
finally:
if storage:
storage.close()
now = time.time()
if f is None:
return None
if item and (dt is None or item[0] > now - dt):
return item[1]
value = f()
storage = self._open_shelf_with_lock()
try:
storage[key] = (now, value)
storage[CacheAbstract.cache_stats_name] = {
'hit_total': storage[CacheAbstract.cache_stats_name]['hit_total'],
'misses': storage[CacheAbstract.cache_stats_name]['misses'] + 1
}
storage.sync()
finally:
if storage:
storage.close()
self._close_shelve_and_unlock()
return value
def increment(self, key, value=1):
storage = self._open_shelf_with_lock()
self.initialize()
storage = self._open_shelve_and_lock()
try:
if key in storage:
value = storage[key][1] + value
storage[key] = (time.time(), value)
storage.sync()
finally:
if storage:
storage.close()
self._close_shelve_and_unlock()
return value
class CacheAction(object):
@@ -423,10 +408,9 @@ class Cache(object):
the global request object
"""
# GAE will have a special caching
if have_settings and settings.global_settings.web2py_runtime_gae:
from contrib.gae_memcache import MemcacheClient
self.ram=self.disk=MemcacheClient(request)
self.ram = self.disk = MemcacheClient(request)
else:
# Otherwise use ram (and try also disk)
self.ram = CacheInRam(request)
@@ -479,6 +463,16 @@ class Cache(object):
return CacheAction(func,key,time_expire,self,cache_model)
return tmp
@staticmethod
def with_prefix(cache_model, prefix):
"""
allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
it will add prefix to all the cache keys used.
"""
return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix:\
cache_model(prefix + key, f, time_expire)
def lazy_cache(key=None,time_expire=None,cache_model='ram'):
"""
can be used to cache any function including in modules,
+29 -27
View File
@@ -40,6 +40,7 @@ import imp
import logging
logger = logging.getLogger("web2py")
import rewrite
from custom_import import custom_import_install
try:
import py_compile
@@ -358,22 +359,36 @@ OLD IMPLEMENTATION:
return module
"""
_base_environment_ = dict((k,getattr(html,k)) for k in html.__all__)
_base_environment_.update((k,getattr(validators,k)) for k in validators.__all__)
_base_environment_['__builtins__'] = __builtins__
_base_environment_['HTTP'] = HTTP
_base_environment_['redirect'] = redirect
_base_environment_['DAL'] = DAL
_base_environment_['Field'] = Field
_base_environment_['SQLDB'] = SQLDB # for backward compatibility
_base_environment_['SQLField'] = SQLField # for backward compatibility
_base_environment_['SQLFORM'] = SQLFORM
_base_environment_['SQLTABLE'] = SQLTABLE
_base_environment_['LOAD'] = LOAD
def build_environment(request, response, session, store_current=True):
"""
Build the environment dictionary into which web2py files are executed.
"""
h,v = html,validators
environment = dict((k,getattr(h,k)) for k in h.__all__)
environment.update((k,getattr(v, k)) for k in v.__all__)
#h,v = html,validators
environment = dict(_base_environment_)
if not request.env:
request.env = Storage()
# Enable standard conditional models (i.e., /*.py, /[controller]/*.py, and
# /[controller]/[function]/*.py)
response.models_to_run = [r'^\w+\.py$', r'^%s/\w+\.py$' % request.controller,
r'^%s/%s/\w+\.py$' % (request.controller, request.function)]
t = environment['T'] = translator(request)
c = environment['cache'] = Cache(request)
if store_current:
current.globalenv = environment
current.request = request
@@ -389,27 +404,17 @@ def build_environment(request, response, session, store_current=True):
__builtins__ = mybuiltin()
else:
__builtins__['__import__'] = __builtin__.__import__ ### WHY?
environment['__builtins__'] = __builtins__
environment['HTTP'] = HTTP
environment['redirect'] = redirect
environment['request'] = request
environment['response'] = response
environment['session'] = session
environment['DAL'] = DAL
environment['Field'] = Field
environment['SQLDB'] = SQLDB # for backward compatibility
environment['SQLField'] = SQLField # for backward compatibility
environment['SQLFORM'] = SQLFORM
environment['SQLTABLE'] = SQLTABLE
environment['LOAD'] = LOAD
environment['local_import'] = \
lambda name, reload=False, app=request.application:\
local_import_aux(name,reload,app)
lambda name, reload=False, app=request.application:\
local_import_aux(name,reload,app)
BaseAdapter.set_folder(pjoin(request.folder, 'databases'))
response._view_environment = copy.copy(environment)
custom_import_install()
return environment
def save_pyc(filename):
"""
Bytecode compiles the file `filename`
@@ -527,7 +532,6 @@ def run_controller_in(controller, function, environment):
"""
# if compiled should run compiled!
folder = environment['request'].folder
path = pjoin(folder, 'compiled')
badc = 'invalid controller (%s/%s)' % (controller, function)
@@ -537,7 +541,7 @@ def run_controller_in(controller, function, environment):
% (controller, function))
if not os.path.exists(filename):
raise HTTP(404,
rewrite.thread.routes.error_message % badf,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
restricted(read_pyc(filename), environment, layer=filename)
elif function == '_TEST':
@@ -552,7 +556,7 @@ def run_controller_in(controller, function, environment):
% controller)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.thread.routes.error_message % badc,
rewrite.THREAD_LOCAL.routes.error_message % badc,
web2py_error=badc)
environment['__symbols__'] = environment.keys()
code = read_file(filename)
@@ -563,13 +567,13 @@ def run_controller_in(controller, function, environment):
% controller)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.thread.routes.error_message % badc,
rewrite.THREAD_LOCAL.routes.error_message % badc,
web2py_error=badc)
code = read_file(filename)
exposed = regex_expose.findall(code)
if not function in exposed:
raise HTTP(404,
rewrite.thread.routes.error_message % badf,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
code = "%s\nresponse._vars=response._caller(%s)\n" % (code, function)
if is_gae:
@@ -579,8 +583,7 @@ def run_controller_in(controller, function, environment):
response = environment['response']
vars=response._vars
if response.postprocessing:
for p in response.postprocessing:
vars = p(vars)
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
if isinstance(vars,unicode):
vars = vars.encode('utf8')
elif hasattr(vars,'xml') and callable(vars.xml):
@@ -594,7 +597,6 @@ def run_view_in(environment):
or `view/generic.extension`
It tries the pre-compiled views_controller_function.pyc before compiling it.
"""
request = environment['request']
response = environment['response']
view = response.view
@@ -630,7 +632,7 @@ def run_view_in(environment):
restricted(code, environment, layer=filename)
return
raise HTTP(404,
rewrite.thread.routes.error_message % badv,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
else:
filename = pjoin(folder, 'views', view)
@@ -639,7 +641,7 @@ def run_view_in(environment):
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.thread.routes.error_message % badv,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
layer = filename
if is_gae:
+22 -13
View File
@@ -12,47 +12,56 @@ cache.ram=cache.disk=MemcacheClient(request)
import time
from google.appengine.api.memcache import Client
class MemcacheClient(object):
class MemcacheClient(Client):
client = Client()
def __init__(self, request):
self.request = request
Client.__init__(self)
def __call__(
self,
key,
f,
time_expire=300,
):
):
key = '%s/%s' % (self.request.application, key)
dt = time_expire
value = None
obj = self.get(key)
obj = self.client.get(key)
if obj and (dt == None or obj[0] > time.time() - dt):
value = obj[1]
elif f is None:
if obj:
self.delete(key)
self.client.delete(key)
else:
value = f()
self.set(key, (time.time(), value))
self.client.set(key, (time.time(), value))
return value
def increment(self, key, value=1):
key = '%s/%s' % (self.request.application, key)
obj = self.get(key)
obj = self.client.get(key)
if obj:
value = obj[1] + value
self.set((time.time(), value))
self.client.set(key, (time.time(), value))
return value
def clear(self, key):
key = '%s/%s' % (self.request.application, key)
self.delete(key)
def clear(self, key = None):
if key:
key = '%s/%s' % (self.request.application, key)
self.client.delete(key)
else:
self.client.flush_all()
def delete(self,*a,**b):
return self.client.delete(*a,**b)
def get(self,*a,**b):
return self.client.delete(*a,**b)
def set(self,*a,**b):
return self.client.delete(*a,**b)
def flush_all(self,*a,**b):
return self.client.delete(*a,**b)
+36 -27
View File
@@ -2,9 +2,9 @@
# coding: utf8
"""
Dropbox Authentication for web2py
Developed by Massimo Di Pierro (2012)
Same License as Web2py License
Dropbox Authentication for web2py
Developed by Massimo Di Pierro (2012)
Same License as Web2py License
"""
# mind here session is dropbox session, not current.session
@@ -27,7 +27,7 @@ class DropboxAccount(object):
key="...",
secret="...",
access_type="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
login_url = "http://localhost:8000/%s/default/user/login" % request.application)
when logged in
client = auth.settings.login_form.client
"""
@@ -40,7 +40,7 @@ class DropboxAccount(object):
login_url = "",
on_login_failure=None,
):
self.request=request
self.key=key
self.secret=secret
@@ -53,39 +53,48 @@ class DropboxAccount(object):
def get_user(self):
request = self.request
token = current.session.dropbox_token
if not token:
return None
try:
self.sess.set_token(token[0],token[1])
except:
# invalid token, should never happen
if not current.session.dropbox_request_token:
return None
elif not current.session.dropbox_access_token:
request_token = current.session.dropbox_request_token
self.sess.set_request_token(request_token[0],request_token[1])
access_token = self.sess.obtain_access_token(self.sess.token)
current.session.dropbox_access_token = \
(access_token.key,access_token.secret)
else:
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
display_name = data.get('display_name','').split(' ',1)
user = dict(email = data.get('email',None),
first_name = display_name[0],
last_name = display_name[-1],
registration_id = data.get('uid',None))
if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure)
return user
access_token = current.session.dropbox_access_token
self.sess.set_token(access_token[0],access_token[1])
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
display_name = data.get('display_name','').split(' ',1)
user = dict(email = data.get('email',None),
first_name = display_name[0],
last_name = display_name[-1],
registration_id = data.get('uid',None))
if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure)
return user
def login_form(self):
token = self.sess.obtain_request_token()
current.session.dropbox_token = (token.key,token.secret)
dropbox_url = self.sess.build_authorize_url(token,self.login_url)
request_token = self.sess.obtain_request_token()
current.session.dropbox_request_token = \
(request_token.key,request_token.secret)
dropbox_url = self.sess.build_authorize_url(request_token,
self.login_url)
redirect(dropbox_url)
form = IFRAME(_src=dropbox_url,
_scrolling="no",
_frameborder="no",
_style="width:400px;height:240px;")
return form
def logout_url(self, next = "/"):
current.session.dropbox_token=None
current.session.dropbox_request_token=None
current.session.auth=None
redirect('https://www.dropbox.com/logout')
return next
@@ -107,7 +107,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
path_info = next
else:
path_info = r.env.path_info
uri = '%s://%s%s' %(url_scheme, http_host, path_info)
uri = '%s://%s%s' % (url_scheme, http_host, path_info)
if r.get_vars and not next:
uri += '?' + urlencode(r.get_vars)
return uri
+12 -5
View File
@@ -26,8 +26,10 @@ class MemcacheClientObj(Client):
def __init__(self, request, servers, debug=0, pickleProtocol=0,
pickler=pickle.Pickler, unpickler=pickle.Unpickler,
pload=None, pid=None):
pload=None, pid=None,
default_time_expire = DEFAULT_TIME_EXPIRE):
self.request=request
self.default_time_expire = default_time_expire
if request:
app = request.application
else:
@@ -43,8 +45,9 @@ class MemcacheClientObj(Client):
else:
self.storage = self.meta_storage[app]
def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE):
def __call__(self, key, f, time_expire = 'default'):
if time_expire == 'default':
time_expire = self.default_time_expire
if time_expire == None:
time_expire = self.max_time_expire
# this must be commented because get and set are redefined
@@ -70,8 +73,10 @@ class MemcacheClientObj(Client):
self.set(key, (now,value), self.max_time_expire)
return value
def increment(self, key, value=1, time_expire=DEFAULT_TIME_EXPIRE):
def increment(self, key, value=1, time_expire='default'):
""" time_expire is ignored """
if time_expire == 'default':
time_expire = self.default_time_expire
newKey = self.__keyFormat__(key)
obj = Client.get(self, newKey)
if obj:
@@ -86,7 +91,9 @@ class MemcacheClientObj(Client):
Client.set(self, newKey, value, self.max_time_expire)
return value
def set(self, key, value, time_expire=DEFAULT_TIME_EXPIRE):
def set(self, key, value, time_expire='default'):
if time_expire == 'default':
time_expire = self.default_time_expire
newKey = self.__keyFormat__(key)
return Client.set(self, newKey, value, time_expire)
+35 -15
View File
@@ -12,6 +12,7 @@ import cssmin
import jsmin
import os
import hashlib
import re
def read_binary_file(filename):
f = open(filename,'rb')
@@ -24,9 +25,10 @@ def write_binary_file(filename,data):
f.write(data)
f.close()
def fix_links(css,static_path):
return css.replace('../',static_path+'/')
def fix_links(css,static_path):
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
def minify(files, path_info, folder, optimize_css, optimize_js,
ignore_concat = [],
ignore_minify = ['/jquery.js', '/anytime.js']):
@@ -60,17 +62,30 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
processed = []
for k,filename in enumerate(files):
if not filename.startswith('/') or \
any(filename.endswith(x) for x in ignore_concat):
any(filename.endswith(x) \
for x in ignore_concat):
new_files.append(filename)
continue
abs_filename = os.path.join(folder,'static',
filename[len(static_path)+1:])
abs_filename = os.path.join(
folder,'static', filename[len(static_path)+1:])
if filename.lower().endswith('.css'):
processed.append(filename)
spath_info, sfilename = \
path_info.split('/'), filename.split('/')
u = 0
for i,a in enumerate(sfilename):
try:
if a != spath_info[i]:
u = i
break
except:
pass
if concat_css:
contents = read_binary_file(abs_filename)
replacement = '/'.join(spath_info[:u]) + '/'
contents = fix_links(contents, replacement)
if minify_css:
css.append(cssmin.cssmin(contents))
else:
@@ -80,9 +95,12 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
elif filename.lower().endswith('.js'):
processed.append(filename)
if concat_js:
contents = read_binary_file(abs_filename)
if minify_js and not filename.endswith('.min.js') and \
not any(filename.endswith(x) for x in ignore_minify):
contents = read_binary_file(abs_filename)
if minify_js and \
not filename.endswith('.min.js') and \
not any(filename.endswith(x) \
for x in ignore_minify):
js.append(jsmin.jsmin(contents))
else:
js.append(contents)
@@ -91,16 +109,17 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
dest_key = hashlib.md5(repr(processed)).hexdigest()
if css and concat_css:
css = '\n\n'.join(contents for contents in css)
if inline_css:
css = ('css:inline',fix_links(css,static_path))
else:
if not inline_css:
temppath = os.path.join(folder,'static',temp)
if not os.path.exists(temppath): os.mkdir(temppath)
if not os.path.exists(temppath):
os.mkdir(temppath)
dest = "compressed_%s.css" % dest_key
tempfile = os.path.join(temppath, dest)
write_binary_file(tempfile,css)
css = path_info+'/%s' % dest
new_files.append(css)
new_files.append(css)
else:
new_files.append(('css:inline',css))
else:
new_files += css
if js and concat_js:
@@ -109,7 +128,8 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
js = ('js:inline',js)
else:
temppath = os.path.join(folder,'static',temp)
if not os.path.exists(temppath): os.mkdir(temppath)
if not os.path.exists(temppath):
os.mkdir(temppath)
dest = "compressed_%s.js" % dest_key
tempfile = os.path.join(folder,'static',temp,dest)
write_binary_file(tempfile,js)
+19
View File
@@ -0,0 +1,19 @@
# Copyright (c) 2006-2012 James Tauber and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+43
View File
@@ -0,0 +1,43 @@
# pyuca: Python Unicode Collation Algorithm implementation
(http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/)
This is my preliminary attempt at a Python implementation of the
[Unicode Collation Algorithm (UCA)](http://unicode.org/reports/tr10/).
I originally posted it to my blog in 2006 but it seems to get enough
usage it really belongs here (and in PyPI).
What do you use it for? In short, sorting non-English strings properly.
The core of the algorithm involves multi-level comparison. For example,
``café`` comes before ``caff`` because at the primary level, the accent
is ignored and the first word is treated as if it were ``cafe``.
The secondary level (which considers accents) only applies then to words
that are equivalent at the primary level.
The Unicode Collation Algorithm and pyuca also support contraction and
expansion. **Contraction** is where multiple letters are treated as a
single unit. In Spanish, ``ch`` is treated as a letter coming between
``c`` and ``d`` so that, for example, words beginning ``ch`` should
sort after all other words beginnings with ``c``. **Expansion** is where
a single letter is treated as though it were multiple letters. In German,
``ä`` is sorted as if it were ``ae``, i.e. after ``ad`` but before ``af``.
## Here is how to use the ``pyuca`` module:
``
git clone https://github.com/jtauber/pyuca.git
cd pyuca
pip install pyuca
``
**Usage example:**
``
from pyuca import Collator
c = Collator("allkeys.txt")
sorted_words = sorted(words, key=c.sort_key)
``
``allkeys.txt`` (1 MB) is available at
http://www.unicode.org/Public/UCA/latest/allkeys.txt
+6 -1
View File
@@ -1,5 +1,10 @@
import os
import pyuca
unicode_collator = pyuca.Collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))
unicode_collator = None
def set_unicode_collator(file):
global unicode_collator
unicode_collator = pyuca.Collator(file)
set_unicode_collator(os.path.join(os.path.dirname(__file__), 'allkeys.txt'))
+38 -27
View File
@@ -1,21 +1,21 @@
# pyuca - Unicode Collation Algorithm
# Version: 2006-02-13
# Version: 2012-06-21
#
# James Tauber
# http://jtauber.com/
# Copyright (c) 2006 James Tauber
#
# Copyright (c) 2006-2012 James Tauber and contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -28,7 +28,6 @@
"""
Preliminary implementation of the Unicode Collation Algorithm.
This only implements the simple parts of the algorithm but I have successfully
tested it using the Default Unicode Collation Element Table (DUCET) to collate
Ancient Greek correctly.
@@ -48,31 +47,39 @@ but you can always subset this for just the characters you are dealing with.
"""
class Trie:
class Node:
def __init__(self):
self.root = [None, {}]
self.value = None
self.children = {}
class Trie:
def __init__(self):
self.root = Node()
def add(self, key, value):
curr_node = self.root
for part in key:
curr_node = curr_node[1].setdefault(part, [None, {}])
curr_node[0] = value
curr_node = curr_node.children.setdefault(part, Node())
curr_node.value = value
def find_prefix(self, key):
curr_node = self.root
remainder = key
for part in key:
if part not in curr_node[1]:
if part not in curr_node.children:
break
curr_node = curr_node[1][part]
curr_node = curr_node.children[part]
remainder = remainder[1:]
return (curr_node[0], remainder)
return (curr_node.value, remainder)
class Collator:
def __init__(self, filename):
self.table = Trie()
self.load(filename)
@@ -85,7 +92,7 @@ class Collator:
line = line[:line.find("#")] + "\n"
line = line[:line.find("%")] + "\n"
line = line.strip()
if line.startswith("@"):
pass
else:
@@ -96,32 +103,36 @@ class Collator:
while True:
begin = x.find("[")
if begin == -1:
break
break
end = x[begin:].find("]")
collElement = x[begin:begin+end+1]
x = x[begin + 1:]
alt = collElement[1]
chars = collElement[2:-1].split(".")
collElements.append((alt, chars))
integer_points = [int(ch, 16) for ch in charList]
self.table.add(integer_points, collElements)
def sort_key(self, string):
collation_elements = []
lookup_key = [ord(ch) for ch in string]
while lookup_key:
value, lookup_key = self.table.find_prefix(lookup_key)
if not value:
# @@@
raise ValueError, map(hex, lookup_key)
# Calculate implicit weighting for CJK Ideographs
# contributed by David Schneider 2009-07-27
# http://www.unicode.org/reports/tr10/#Implicit_Weights
value = []
value.append((".", ["%X" % (0xFB40 + (lookup_key[0] >> 15)), "0020", "0002", "0001"]))
value.append((".", ["%X" % ((lookup_key[0] & 0x7FFF) | 0x8000), "0000", "0000", "0000"]))
lookup_key = lookup_key[1:]
collation_elements.extend(value)
sort_key = []
for level in range(4):
if level:
sort_key.append(0) # level separator
@@ -129,5 +140,5 @@ class Collator:
ce_l = int(element[1][level], 16)
if ce_l:
sort_key.append(ce_l)
return tuple(sort_key)
+201
View File
@@ -0,0 +1,201 @@
"""
Developed by niphlod@gmail.com
"""
import redis
from redis.exceptions import ConnectionError
from gluon import current
from gluon.storage import Storage
import cPickle as pickle
import time
import re
import logging
import thread
logger = logging.getLogger("web2py.session.redis")
locker = thread.allocate_lock()
def RedisSession(*args, **vars):
"""
Usage example: put in models
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
session.connect(request, response, db = sessiondb)
Simple slip-in storage for session
"""
locker.acquire()
try:
if not hasattr(RedisSession, 'redis_instance'):
RedisSession.redis_instance = RedisClient(*args, **vars)
finally:
locker.release()
return RedisSession.redis_instance
class RedisClient(object):
meta_storage = {}
MAX_RETRIES = 5
RETRIES = 0
def __init__(self, server='localhost:6379', db=None, debug=False, session_expiry=False):
"""session_expiry can be an integer, in seconds, to set the default expiration
of sessions. The corresponding record will be deleted from the redis instance,
and there's virtually no need to run sessions2trash.py
"""
self.server = server
self.db = db or 0
host,port = (self.server.split(':')+['6379'])[:2]
port = int(port)
self.debug = debug
if current and current.request:
self.app = current.request.application
else:
self.app = ''
self.r_server = redis.Redis(host=host, port=port, db=self.db)
self.tablename = None
self.session_expiry = session_expiry
def get(self, what, default):
return self.tablename
def Field(self, fieldname, type='string', length=None, default=None,
required=False,requires=None):
return None
def define_table(self,tablename,*fields,**args):
if not self.tablename:
self.tablename = MockTable(self, self.r_server, tablename, self.session_expiry)
return self.tablename
def __getitem__(self, key):
return self.tablename
def __call__(self, where=''):
q = self.tablename.query
return q
def commit(self):
#this is only called by session2trash.py
pass
class MockTable(object):
def __init__(self, db, r_server, tablename, session_expiry):
self.db = db
self.r_server = r_server
self.tablename = tablename
#set the namespace for sessions of this app
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '')
#fast auto-increment id (needed for session handling)
self.serial = "%s:serial" % self.keyprefix
#index of all the session keys of this app
self.id_idx = "%s:id_idx" % self.keyprefix
#remember the session_expiry setting
self.session_expiry = session_expiry
def getserial(self):
#return an auto-increment id
return "%s" % self.r_server.incr(self.serial, 1)
def __getattr__(self, key):
if key == 'id':
#return a fake query. We need to query it just by id for normal operations
self.query = MockQuery(field='id', db=self.r_server, prefix=self.keyprefix, session_expiry=self.session_expiry)
return self.query
elif key == '_db':
#needed because of the calls in sessions2trash.py and globals.py
return self.db
def insert(self, **kwargs):
#usually kwargs would be a Storage with several keys:
#'locked', 'client_ip','created_datetime','modified_datetime'
#'unique_key', 'session_data'
#retrieve a new key
newid = self.getserial()
key = "%s:%s" % (self.keyprefix, newid)
#add it to the index
self.r_server.sadd(self.id_idx, key)
#set a hash key with the Storage
self.r_server.hmset(key, kwargs)
if self.session_expiry:
self.r_server.expire(key, self.session_expiry)
return newid
class MockQuery(object):
"""a fake Query object that supports querying by id
and listing all keys. No other operation is supported
"""
def __init__(self, field=None, db=None, prefix=None, session_expiry=False):
self.field = field
self.value = None
self.db = db
self.keyprefix = prefix
self.op = None
self.session_expiry = session_expiry
def __eq__(self, value, op='eq'):
self.value = value
self.op = op
def __gt__(self, value, op='ge'):
self.value = value
self.op = op
def select(self):
if self.op == 'eq' and self.field == 'id' and self.value:
#means that someone wants to retrieve the key self.value
rtn = self.db.hgetall("%s:%s" % (self.keyprefix, self.value))
if rtn == dict():
#return an empty resultset for non existing key
return []
else:
return [Storage(rtn)]
elif self.op == 'ge' and self.field == 'id' and self.value == 0:
#means that someone wants the complete list
rtn = []
id_idx = "%s:id_idx" % self.keyprefix
#find all session keys of this app
allkeys = self.db.smembers(id_idx)
for sess in allkeys:
val = self.db.hgetall(sess)
if val == dict():
if self.session_expiry:
#clean up the idx, because the key expired
self.db.srem(id_idx, sess)
continue
else:
continue
val = Storage(val)
#add a delete_record method (necessary for sessions2trash.py)
val.delete_record = RecordDeleter(self.db, sess, self.keyprefix)
rtn.append(val)
return rtn
else:
raise Exception("Operation not supported")
def update(self, **kwargs):
#means that the session has been found and needs an update
if self.op == 'eq' and self.field == 'id' and self.value:
rtn = self.db.hmset("%s:%s" % (self.keyprefix, self.value), kwargs)
if self.session_expiry:
self.db.expire(key, self.session.expiry)
return rtn
class RecordDeleter(object):
"""Dumb record deleter to support sessions2trash.py"""
def __init__(self, db, key, keyprefix):
self.db, self.key, self.keyprefix = db, key, keyprefix
def __call__(self):
id_idx = "%s:id_idx" % self.keyprefix
#remove from the index
self.db.srem(id_idx, self.key)
#remove the key itself
self.db.delete(self.key)
@@ -12,18 +12,18 @@ Attention: Requires Chrome or Safari. For IE of Firefox you need https://github.
2) start this app:
python gluon/contrib/comet_messaging.py -k mykey -p 8888
python gluon/contrib/websocket_messaging.py -k mykey -p 8888
3) from any web2py app you can post messages with
from gluon.contrib.comet_messaging import comet_send
comet_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
from gluon.contrib.websocket_messaging import websocket_send
websocket_send('http://127.0.0.1:8888','Hello World','mykey','mygroup')
4) from any template you can receive them with
<script>
$(document).ready(function(){
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
if(!web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){alert(e.data)}))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
</script>
@@ -34,14 +34,14 @@ Or if you want to send json messages and store evaluated json in a var called da
<script>
$(document).ready(function(){
var data;
web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',function(e){data=eval('('+e.data+')')});
});
</script>
- All communications between web2py and comet_messaging will be digitally signed with hmac.
- All validation is handled on the web2py side and there is no need to modify comet_messaging.py
- Multiple web2py instances can talk with one or more comet_messaging servers.
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the comet_messaging server.
- All communications between web2py and websocket_messaging will be digitally signed with hmac.
- All validation is handled on the web2py side and there is no need to modify websocket_messaging.py
- Multiple web2py instances can talk with one or more websocket_messaging servers.
- "ws://127.0.0.1:8888/realtime/" must be contain the IP of the websocket_messaging server.
- Via group='mygroup' name you can support multiple groups of clients (think of many chat-rooms)
Here is a complete sample web2py action:
@@ -51,7 +51,7 @@ Here is a complete sample web2py action:
script=SCRIPT('''
jQuery(document).ready(function(){
var callback=function(e){alert(e.data)};
if(!web2py_comet('ws://127.0.0.1:8888/realtime/mygroup',callback))
if(!web2py_websocket('ws://127.0.0.1:8888/realtime/mygroup',callback))
alert("html5 websocket not supported by your browser, try Google Chrome");
});
''')
@@ -60,8 +60,8 @@ Here is a complete sample web2py action:
def ajax_form():
form=SQLFORM.factory(Field('message'))
if form.accepts(request,session):
from gluon.contrib.comet_messaging import comet_send
comet_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
from gluon.contrib.websocket_messaging import websocket_send
websocket_send('http://127.0.0.1:8888',form.vars.message,'mykey','mygroup')
return form
Acknowledgements:
@@ -83,7 +83,7 @@ listeners = {}
names = {}
tokens = {}
def comet_send(url,message,hmac_key=None,group='default'):
def websocket_send(url,message,hmac_key=None,group='default'):
sig = hmac_key and hmac.new(hmac_key,message).hexdigest() or ''
params = urllib.urlencode({'message': message, 'signature': sig, 'group':group})
f = urllib.urlopen(url, params)
+94 -239
View File
@@ -6,153 +6,137 @@ import os
import re
import sys
import threading
import traceback
from gluon import current
# Install the new import function:
def custom_import_install(web2py_path):
global _web2py_importer
global _web2py_path
if isinstance(__builtin__.__import__, _Web2pyImporter):
return #aready installed
_web2py_path = web2py_path
_web2py_importer = _Web2pyImporter(web2py_path)
__builtin__.__import__ = _web2py_importer
NATIVE_IMPORTER = __builtin__.__import__
INVALID_MODULES = set(('','gluon','applications','custom_import'))
def is_tracking_changes():
"""
@return: True: neo_importer is tracking changes made to Python source
files. False: neo_import does not reload Python modules.
"""
global _is_tracking_changes
return _is_tracking_changes
# backward compatibility API
def custom_import_install():
if __builtin__.__import__ != custom_importer:
INVALID_MODULES.update(sys.modules.keys())
__builtin__.__import__ = custom_importer
def track_changes(track=True):
assert track in (True,False), "must be True or False"
current.request._custom_import_track_changes = track
def is_tracking_changes():
return current.request._custom_import_track_changes
class CustomImportException(ImportError):
pass
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
"""
Tell neo_importer to start/stop tracking changes made to Python modules.
@param track: True: Start tracking changes. False: Stop tracking changes.
The web2py custom importer. Like the standard Python importer but it
tries to transform import statements as something like
"import applications.app_name.modules.x".
If the import failed, fall back on naive_importer
"""
global _is_tracking_changes
global _web2py_importer
global _web2py_date_tracker_importer
assert track is True or track is False, "Boolean expected."
if track == _is_tracking_changes:
return
if track:
if not _web2py_date_tracker_importer:
_web2py_date_tracker_importer = \
_Web2pyDateTrackerImporter(_web2py_path)
__builtin__.__import__ = _web2py_date_tracker_importer
else:
__builtin__.__import__ = _web2py_importer
_is_tracking_changes = track
globals = globals or {}
locals = locals or {}
fromlist = fromlist or []
_STANDARD_PYTHON_IMPORTER = __builtin__.__import__ # Keep standard importer
_web2py_importer = None # The standard web2py importer
_web2py_date_tracker_importer = None # The web2py importer with date tracking
_web2py_path = None # Absolute path of the web2py directory
try:
if current.request._custom_import_track_changes:
base_importer = TRACK_IMPORTER
else:
base_importer = NATIVE_IMPORTER
except: # there is no current.request (should never happen)
base_importer = NATIVE_IMPORTER
_is_tracking_changes = False # The tracking mode
# if not relative and not from applications:
if hasattr(current,'request') \
and level<=0 \
and not name.split('.')[0] in INVALID_MODULES \
and isinstance(globals, dict):
import_tb = None
try:
items = current.request.folder.split(os.path.sep)
if not items[-1]: items = items[:-1]
modules_prefix = '.'.join(items[-2:])+'.modules'
if not fromlist:
# import like "import x" or "import x.y"
result = None
for itemname in name.split("."):
new_mod = base_importer(
modules_prefix, globals,locals, [itemname], level)
try:
result = result or new_mod.__dict__[itemname]
except KeyError, e:
raise ImportError, 'Cannot import module %s' % str(e)
modules_prefix += "." + itemname
return result
else:
# import like "from x import a, b, ..."
pname = modules_prefix + "." + name
return base_importer(pname, globals, locals, fromlist, level)
except ImportError, e1:
import_tb = sys.exc_info()[2]
try:
return NATIVE_IMPORTER(name,globals,locals,fromlist,level)
except ImportError, e3:
raise ImportError, e1, import_tb # there an import error in the module
except Exception, e2:
raise e2 # there is an error in the module
finally:
if import_tb:
import_tb = None
class _BaseImporter(object):
"""
The base importer. Dispatch the import the call to the standard Python
importer.
"""
def begin(self):
"""
Many imports can be made for a single import statement. This method
help the management of this aspect.
"""
def __init__(self):
self._STANDARD_PYTHON_IMPORTER = _STANDARD_PYTHON_IMPORTER
def __call__(self, name, globals=None, locals=None,
fromlist=None, level=-1):
"""
The import method itself.
"""
return self._STANDARD_PYTHON_IMPORTER(name,
globals,
locals,
fromlist,
level)
def end(self):
"""
Needed for clean up.
"""
return NATIVE_IMPORTER(name,globals,locals,fromlist,level)
class _DateTrackerImporter(_BaseImporter):
class TrackImporter(object):
"""
An importer tracking the date of the module files and reloading them when
they have changed.
"""
_PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
THREAD_LOCAL = threading.local()
PACKAGE_PATH_SUFFIX = os.path.sep+"__init__.py"
def __init__(self):
super(_DateTrackerImporter, self).__init__()
self._import_dates = {} # Import dates of the files of the modules
# Avoid reloading cause by file modifications of reload:
self._tl = threading.local()
self._tl._modules_loaded = None
def begin(self):
self._tl._modules_loaded = set()
def __call__(self, name, globals=None, locals=None,
fromlist=None, level=-1):
def __call__(self,name,globals=None,locals=None,fromlist=None,level=-1):
"""
The import method itself.
"""
globals = globals or {}
locals = locals or {}
fromlist = fromlist or []
call_begin_end = self._tl._modules_loaded is None
if call_begin_end:
self.begin()
if not hasattr(self.THREAD_LOCAL,'_modules_loaded'):
self.THREAD_LOCAL._modules_loaded = set()
try:
self._tl.globals = globals
self._tl.locals = locals
self._tl.level = level
# Check the date and reload if needed:
self._update_dates(name, fromlist)
self._update_dates(name, globals, locals, fromlist, level)
# Try to load the module and update the dates if it works:
result = super(_DateTrackerImporter, self) \
.__call__(name, globals, locals, fromlist, level)
result = NATIVE_IMPORTER(name, globals, locals, fromlist, level)
# Module maybe loaded for the 1st time so we need to set the date
self._update_dates(name, fromlist)
self._update_dates(name, globals, locals, fromlist, level)
return result
except Exception:
except Exception, e:
raise # Don't hide something that went wrong
finally:
if call_begin_end:
self.end()
def _update_dates(self, name, fromlist):
def _update_dates(self, name, globals, locals, fromlist, level):
"""
Update all the dates associated to the statement import. A single
import statement may import many modules.
"""
self._reload_check(name)
if fromlist:
for fromlist_name in fromlist:
self._reload_check("%s.%s" % (name, fromlist_name))
self._reload_check(name, globals, locals, level)
for fromlist_name in fromlist or []:
pname = "%s.%s" % (name, fromlist_name)
self._reload_check(pname, globals, locals, level)
def _reload_check(self, name):
def _reload_check(self, name, globals, locals, level):
"""
Update the date associated to the module and reload the module if
the file has changed.
"""
module = sys.modules.get(name)
file = self._get_module_file(module)
if file:
@@ -170,7 +154,7 @@ class _DateTrackerImporter(_BaseImporter):
# Get path without file ext:
file = os.path.splitext(file)[0]
reload_mod = os.path.isdir(file) \
and os.path.isfile(file+self._PACKAGE_PATH_SUFFIX)
and os.path.isfile(file+self.PACKAGE_PATH_SUFFIX)
mod_to_pack = reload_mod
else: # Package turning into module?
file += ".py"
@@ -180,156 +164,27 @@ class _DateTrackerImporter(_BaseImporter):
if reload_mod or not date or new_date > date:
self._import_dates[file] = new_date
if reload_mod or (date and new_date > date):
if module not in self._tl._modules_loaded:
if module not in self.THREAD_LOCAL._modules_loaded:
if mod_to_pack:
# Module turning into a package:
mod_name = module.__name__
del sys.modules[mod_name] # Delete the module
# Reload the module:
super(_DateTrackerImporter, self).__call__ \
(mod_name, self._tl.globals, self._tl.locals, [],
self._tl.level)
NATIVE_IMPORTER(mod_name, globals, locals, [], level)
else:
reload(module)
self._tl._modules_loaded.add(module)
self.THREAD_LOCAL._modules_loaded.add(module)
def end(self):
self._tl._modules_loaded = None
@classmethod
def _get_module_file(cls, module):
def _get_module_file(self, module):
"""
Get the absolute path file associated to the module or None.
"""
file = getattr(module, "__file__", None)
if file:
# Make path absolute if not:
#file = os.path.join(cls.web2py_path, file)
file = os.path.splitext(file)[0]+".py" # Change .pyc for .py
if file.endswith(cls._PACKAGE_PATH_SUFFIX):
if file.endswith(self.PACKAGE_PATH_SUFFIX):
file = os.path.dirname(file) # Track dir for packages
return file
class _Web2pyImporter(_BaseImporter):
"""
The standard web2py importer. Like the standard Python importer but it
tries to transform import statements as something like
"import applications.app_name.modules.x". If the import failed, fall back
on _BaseImporter.
"""
_RE_ESCAPED_PATH_SEP = re.escape(os.path.sep) # os.path.sep escaped for re
def __init__(self, web2py_path):
"""
@param web2py_path: The absolute path of the web2py installation.
"""
global DEBUG
self.super_class = super(_Web2pyImporter, self)
self.super_class.__init__()
self.web2py_path = web2py_path
self.__web2py_path_os_path_sep = self.web2py_path+os.path.sep
self.__web2py_path_os_path_sep_len = len(self.__web2py_path_os_path_sep)
self.__RE_APP_DIR = re.compile(
self._RE_ESCAPED_PATH_SEP.join( \
( \
#"^" + re.escape(web2py_path), # Not working with Python 2.5
"^(" + "applications",
"[^",
"]+)",
"",
) ))
def _matchAppDir(self, file_path):
"""
Does the file in a directory inside the "applications" directory?
"""
if file_path.startswith(self.__web2py_path_os_path_sep):
file_path = file_path[self.__web2py_path_os_path_sep_len:]
return self.__RE_APP_DIR.match(file_path)
return False
def __call__(self, name, globals=None, locals=None,
fromlist=None, level=-1):
"""
The import method itself.
"""
globals = globals or {}
locals = locals or {}
fromlist = fromlist or []
self.begin()
#try:
# if not relative and not from applications:
if not name.startswith(".") and level <= 0 \
and not name.startswith("applications.") \
and isinstance(globals, dict):
# Get the name of the file do the import
caller_file_name = os.path.join(self.web2py_path, \
globals.get("__file__", ""))
# Is the path in an application directory?
match_app_dir = self._matchAppDir(caller_file_name)
if match_app_dir:
try:
# Get the prefix to add for the import
# (like applications.app_name.modules):
modules_prefix = \
".".join((match_app_dir.group(1). \
replace(os.path.sep, "."), "modules"))
if not fromlist:
# import like "import x" or "import x.y"
return self.__import__dot(modules_prefix, name,
globals, locals, fromlist, level)
else:
# import like "from x import a, b, ..."
return self.super_class \
.__call__(modules_prefix+"."+name,
globals, locals, fromlist, level)
except ImportError, e:
try:
return self.super_class.__call__(name, globals, locals,
fromlist, level)
except ImportError, e1:
raise e
return self.super_class.__call__(name, globals, locals,
fromlist, level)
def __import__dot(self, prefix, name, globals, locals, fromlist,
level):
"""
Here we will import x.y.z as many imports like:
from applications.app_name.modules import x
from applications.app_name.modules.x import y
from applications.app_name.modules.x.y import z.
x will be the module returned.
"""
result = None
for name in name.split("."):
new_mod = super(_Web2pyImporter, self).__call__(prefix, globals,
locals, [name], level)
try:
result = result or new_mod.__dict__[name]
except KeyError, e:
raise ImportError, 'Cannot import module %s' % str(e)
prefix += "." + name
return result
class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
"""
Like _Web2pyImporter but using a _DateTrackerImporter.
"""
TRACK_IMPORTER = TrackImporter()
+620 -567
View File
File diff suppressed because it is too large Load Diff
+33 -21
View File
@@ -128,6 +128,7 @@ class Request(Storage):
and secure the session.
"""
if not global_settings.cronjob and not self.is_https:
current.session.forget()
redirect(URL(scheme='https', args=self.args, vars=self.vars))
current.session.secure()
@@ -229,7 +230,7 @@ class Response(Storage):
for k,v in (self.meta or {}).iteritems())
self.write(s,escape=False)
def include_files(self):
def include_files(self, extensions=None):
"""
Caching method for writing out files.
@@ -240,11 +241,22 @@ class Response(Storage):
from gluon import URL
files = []
has_js = has_css = False
for item in self.files:
if not item in files: files.append(item)
if have_minify and (self.optimize_css or self.optimize_js):
if extensions and not item.split('.')[-1] in extensions:
continue
if item in files:
continue
if item.endswith('.js'):
has_js = True
if item.endswith('.css'):
has_css = True
files.append(item)
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
# cache for 5 minutes by default
key = hashlib.md5(repr(files)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60*5)
def call_minify(files=files):
return minify.minify(files,
@@ -263,6 +275,8 @@ class Response(Storage):
for item in files:
if isinstance(item,str):
f = item.lower().split('?')[0]
if self.static_version:
item = item.replace('/static/', '/static/_%s/' % self.static_version, 1)
if f.endswith('.css'): s += css_template % item
elif f.endswith('.js'): s += js_template % item
elif f.endswith('.coffee'): s += coffee_template % item
@@ -316,7 +330,8 @@ class Response(Storage):
stream_file_or_304_or_206(stream,
chunk_size=chunk_size,
request=request,
headers=headers)
headers=headers,
status=self.status)
# ## the following is for backward compatibility
if hasattr(stream, 'name'):
@@ -402,15 +417,15 @@ class Response(Storage):
return handler(request, self, methods)
def toolbar(self):
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
BUTTON = TAG.button
admin = URL("admin","default","design",
args=current.request.application)
from gluon.dal import thread
if hasattr(thread,'instances'):
from gluon.dal import THREAD_LOCAL
if hasattr(THREAD_LOCAL,'instances'):
dbstats = [TABLE(*[TR(PRE(row[0]),'%.2fms' % (row[1]*1000)) \
for row in i.db._timings]) \
for i in thread.instances]
for i in THREAD_LOCAL.instances]
dbtables = dict([(regex_nopasswd.sub('******',i.uri),
{'defined':
sorted(list(set(i.db.tables) -
@@ -418,24 +433,26 @@ class Response(Storage):
'[no defined tables]',
'lazy': sorted(i.db._LAZY_TABLES.keys()) or
'[no lazy tables]'})
for i in thread.instances])
for i in THREAD_LOCAL.instances])
else:
dbstats = [] # if no db or on GAE
dbtables = {}
u = web2py_uuid()
backtotop = A('Back to top', _href="#totop-%s" % u)
return DIV(
BUTTON('design',_onclick="document.location='%s'" % admin),
BUTTON('request',_onclick="jQuery('#request-%s').slideToggle()"%u),
DIV(BEAUTIFY(current.request),_class="hidden",_id="request-%s"%u),
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u),
DIV(BEAUTIFY(current.session),_class="hidden",_id="session-%s"%u),
BUTTON('response',_onclick="jQuery('#response-%s').slideToggle()"%u),
DIV(BEAUTIFY(current.response),_class="hidden",_id="response-%s"%u),
BUTTON('session',_onclick="jQuery('#session-%s').slideToggle()"%u),
BUTTON('db tables',_onclick="jQuery('#db-tables-%s').slideToggle()"%u),
DIV(BEAUTIFY(dbtables),_class="hidden",_id="db-tables-%s"%u),
BUTTON('db stats',_onclick="jQuery('#db-stats-%s').slideToggle()"%u),
DIV(BEAUTIFY(dbstats),_class="hidden",_id="db-stats-%s"%u),
DIV(BEAUTIFY(current.request), backtotop,_class="hidden",_id="request-%s"%u),
DIV(BEAUTIFY(current.session), backtotop, _class="hidden",_id="session-%s"%u),
DIV(BEAUTIFY(current.response), backtotop, _class="hidden",_id="response-%s"%u),
DIV(BEAUTIFY(dbtables), backtotop, _class="hidden",_id="db-tables-%s"%u),
DIV(BEAUTIFY(dbstats), backtotop, _class="hidden",_id="db-stats-%s"%u),
SCRIPT("jQuery('.hidden').hide()")
,_id="totop-%s" % u
)
class Session(Storage):
@@ -467,7 +484,7 @@ class Session(Storage):
if not masterapp:
masterapp = request.application
response.session_id_name = 'session_id_%s' % masterapp.lower()
# Load session data from cookie
cookies = request.cookies
@@ -703,8 +720,3 @@ class Session(Storage):
del response.session_file
except:
pass
+38 -16
View File
@@ -32,6 +32,11 @@ regex_crlf = re.compile('\r|\n')
join = ''.join
# name2codepoint is incomplete respect to xhtml (and xml): 'apos' is missing.
entitydefs = dict(map(lambda (k,v): (k, unichr(v).encode('utf-8')), name2codepoint.iteritems()))
entitydefs.setdefault('apos', u"'".encode('utf-8'))
__all__ = [
'A',
'B',
@@ -243,7 +248,7 @@ def URL(
elif a and c and not f: (c,f,a)=(a,c,f)
from globals import current
if hasattr(current,'request'):
r = current.request
r = current.request
if r:
application = r.application
@@ -487,7 +492,7 @@ class XmlComponent(object):
components += [other]
return CAT(*components)
def add_class(self, name):
def add_class(self, name):
""" add a class to _class attribute """
c = self['_class']
classes = (set(c.split()) if c else set())|set(name.split())
@@ -1745,7 +1750,7 @@ class INPUT(DIV):
elif not t == 'submit':
if value is None:
self['value'] = _value
else:
elif not isinstance(value,list):
self['_value'] = value
def xml(self):
@@ -1938,6 +1943,7 @@ class FORM(DIV):
# check formname and formkey
status = True
changed = False
request_vars = self.request_vars
if session:
formkey = session.get('_formkey[%s]' % formname, None)
@@ -1950,18 +1956,23 @@ class FORM(DIV):
# check if editing a record that has been modified by the server
if hasattr(self,'record_hash') and self.record_hash != formkey:
status = False
self.record_changed = True
self.record_changed = changed = True
status = self._traverse(status,hideerror)
status = self.assert_status(status, request_vars)
if onvalidation:
if isinstance(onvalidation, dict):
onsuccess = onvalidation.get('onsuccess', None)
onfailure = onvalidation.get('onfailure', None)
onchange = onvalidation.get('onchange', None)
if onsuccess and status:
onsuccess(self)
if onfailure and request_vars and not status:
onfailure(self)
status = len(self.errors) == 0
if changed:
if onchange and self.record_changed and \
self.detect_record_change:
onchange(self)
elif status:
if isinstance(onvalidation, (list, tuple)):
[f(self) for f in onvalidation]
@@ -2030,8 +2041,13 @@ class FORM(DIV):
onfailure = 'flash' - will show message_onfailure in response.flash
None - will do nothing
can be a function (lambda form: pass)
onchange = 'flash' - will show message_onchange in response.flash
None - will do nothing
can be a function (lambda form: pass)
message_onsuccess
message_onfailure
message_onchange
next = where to redirect in case of success
any other kwargs will be passed for form.accepts(...)
"""
@@ -2042,13 +2058,17 @@ class FORM(DIV):
onsuccess = kwargs.get('onsuccess','flash')
onfailure = kwargs.get('onfailure','flash')
onchange = kwargs.get('onchange', 'flash')
message_onsuccess = kwargs.get('message_onsuccess',
current.T("Success!"))
message_onfailure = kwargs.get('message_onfailure',
current.T("Errors in form, please check it out."))
message_onchange = kwargs.get('message_onchange',
current.T("Form consecutive submissions not allowed. " +
"Try re-submitting or refreshing the form page."))
next = kwargs.get('next',None)
for key in ('message_onsuccess','message_onfailure','onsuccess',
'onfailure','next'):
'onfailure','next', 'message_onchange', 'onchange'):
if key in kwargs:
del kwargs[key]
@@ -2075,6 +2095,13 @@ class FORM(DIV):
elif callable(onfailure):
onfailure(self)
return False
elif hasattr(self, "record_changed"):
if self.record_changed and self.detect_record_change:
if onchange == 'flash':
current.response.flash = message_onchange
elif callable(onchange):
onchange(self)
return False
def process(self, **kwargs):
"""
@@ -2111,8 +2138,10 @@ class FORM(DIV):
REDIRECT_JS = "window.location='%s';return false"
def add_button(self,value,url,_class=None):
self[0][-1][1].append(INPUT(_type="button",_value=value,_class=_class,
_onclick=self.REDIRECT_JS % url))
submit = self.element('input[type=submit]')
submit.parent.append(
INPUT(_type="button",_value=value,_class=_class,
_onclick=self.REDIRECT_JS % url))
@@ -2276,7 +2305,7 @@ class MENU(DIV):
select = SELECT(**self.attributes)
for item in data:
if len(item) <= 4 or item[4] == True:
select.append(OPTION(CAT(prefix, item[0]),
select.append(OPTION(CAT(prefix, item[0]),
_value=item[2], _selected=item[1]))
if len(item)>3 and len(item[3]):
self.serialize_mobile(
@@ -2402,7 +2431,7 @@ class web2pyHTMLParser(HTMLParser):
else:
self.parent.append(unichr(int(name)).encode('utf8'))
def handle_entityref(self,name):
self.parent.append(unichr(name2codepoint[name]).encode('utf8'))
self.parent.append(entitydefs[name])
def handle_endtag(self, tagname):
# this deals with unbalanced tags
if tagname==self.last:
@@ -2506,10 +2535,3 @@ class MARKMIN(XmlComponent):
if __name__ == '__main__':
import doctest
doctest.testmod()
+1
View File
@@ -42,6 +42,7 @@ defined_status = {
415: 'UNSUPPORTED MEDIA TYPE',
416: 'REQUESTED RANGE NOT SATISFIABLE',
417: 'EXPECTATION FAILED',
422: 'UNPROCESSABLE ENTITY',
500: 'INTERNAL SERVER ERROR',
501: 'NOT IMPLEMENTED',
502: 'BAD GATEWAY',
+2 -2
View File
@@ -155,9 +155,9 @@ def read_possible_plural_rules():
create list of all possible plural rules files
result is cached in PLURAL_RULES dictionary to increase speed
"""
plurals = {}
try:
import gluon.contrib.plural_rules as package
plurals = {}
import contrib.plural_rules as package
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
if len(modname)==2:
module = __import__(package.__name__+'.'+modname,
+49 -36
View File
@@ -35,8 +35,6 @@ from settings import global_settings
from admin import add_path_first, create_missing_folders, create_missing_app_folders
from globals import current
from custom_import import custom_import_install
# Remarks:
# calling script has inserted path to script directory into sys.path
# applications_parent (path to applications/, site-packages/ etc)
@@ -55,8 +53,6 @@ from custom_import import custom_import_install
web2py_path = global_settings.applications_parent # backward compatibility
custom_import_install(web2py_path)
create_missing_folders()
# set up logging for subsequent imports
@@ -88,10 +84,10 @@ from contenttype import contenttype
from dal import BaseAdapter
from settings import global_settings
from validators import CRYPT
from cache import Cache
from cache import CacheInRam
from html import URL, xmlescape
from utils import is_valid_ip_address
from rewrite import load, url_in, thread as rwthread, \
from rewrite import load, url_in, THREAD_LOCAL as rwthread, \
try_rewrite_on_error, fixup_missing_path_info
import newcron
@@ -123,6 +119,8 @@ except:
load()
HTTPS_SCHEMES = set(('https','HTTPS'))
def get_client(env):
"""
guess the client address from the environment variables
@@ -132,7 +130,7 @@ def get_client(env):
"""
g = regex_client.search(env.get('http_x_forwarded_for', ''))
if g:
client = g.group()
client = (g.group() or '').split(',')[0]
else:
g = regex_client.search(env.get('remote_addr', ''))
if g:
@@ -146,29 +144,32 @@ def get_client(env):
def copystream_progress(request, chunk_size= 10**5):
"""
copies request.env.wsgi_input into request.body
and stores progress upload status in cache.ram
and stores progress upload status in cache_ram
X-Progress-ID:length and X-Progress-ID:uploaded
"""
env = request.env
if not env.content_length:
return cStringIO.StringIO()
source = env.wsgi_input
size = int(env.content_length)
try:
size = int(env.content_length)
except ValueError:
raise HTTP(400,"Invalid Content-Length header")
dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.vars:
copystream(source, dest, size, chunk_size)
return dest
cache_key = 'X-Progress-ID:'+request.vars['X-Progress-ID']
cache = Cache(request)
cache.ram(cache_key+':length', lambda: size, 0)
cache.ram(cache_key+':uploaded', lambda: 0, 0)
cache_ram = CacheInRam(request) # same as cache.ram because meta_storage
cache_ram(cache_key+':length', lambda: size, 0)
cache_ram(cache_key+':uploaded', lambda: 0, 0)
while size > 0:
if size < chunk_size:
data = source.read(size)
cache.ram.increment(cache_key+':uploaded', size)
cache_ram.increment(cache_key+':uploaded', size)
else:
data = source.read(chunk_size)
cache.ram.increment(cache_key+':uploaded', chunk_size)
cache_ram.increment(cache_key+':uploaded', chunk_size)
length = len(data)
if length > size:
(data, length) = (data[:size], size)
@@ -179,8 +180,8 @@ def copystream_progress(request, chunk_size= 10**5):
if length < chunk_size:
break
dest.seek(0)
cache.ram(cache_key+':length', None)
cache.ram(cache_key+':uploaded', None)
cache_ram(cache_key+':length', None)
cache_ram(cache_key+':uploaded', None)
return dest
@@ -325,10 +326,12 @@ def parse_get_post_vars(request, environ):
dpk = dpost[key]
# if en element is not a file replace it with its value else leave it alone
if isinstance(dpk, list):
if not dpk[0].filename:
value = [x.value for x in dpk]
else:
value = [x for x in dpk]
value = []
for _dpk in dpk:
if not _dpk.filename:
value.append(_dpk.value)
else:
value.append(_dpk)
elif not dpk.filename:
value = dpk.value
else:
@@ -400,7 +403,7 @@ def wsgibase(environ, responder):
# ##################################################
fixup_missing_path_info(environ)
(static_file, environ) = url_in(request, environ)
(static_file, version, environ) = url_in(request, environ)
response.status = env.web2py_status_code or response.status
if static_file:
@@ -408,24 +411,34 @@ def wsgibase(environ, responder):
'attachment'):
response.headers['Content-Disposition'] \
= 'attachment'
if version:
response.headers['Cache-Control'] = 'max-age=315360000'
response.headers['Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
response.stream(static_file, request=request)
# ##################################################
# fill in request items
# ##################################################
app = request.application ## must go after url_in!
http_host = env.http_host.split(':',1)[0]
local_hosts = [http_host,'::1','127.0.0.1',
'::ffff:127.0.0.1']
if not global_settings.web2py_runtime_gae:
local_hosts.append(socket.gethostname())
try:
local_hosts.append(
socket.gethostbyname(http_host))
except socket.gaierror:
pass
client = get_client(env)
if not global_settings.local_hosts:
local_hosts = ['127.0.0.1','::ffff:127.0.0.1']
if not global_settings.web2py_runtime_gae:
try:
local_hosts.append(socket.gethostname())
except TypeError:
pass
try:
if env.server_name:
local_hosts += [
env.server_name,
socket.gethostbyname(env.server_name)]
except (socket.gaierror,TypeError):
pass
global_settings.local_hosts = local_hosts
else:
local_hosts = global_settings.local_hosts
client = get_client(env)
x_req_with = str(env.http_x_requested_with).lower()
request.update(
@@ -434,8 +447,9 @@ def wsgibase(environ, responder):
ajax = x_req_with == 'xmlhttprequest',
cid = env.http_web2py_component_element,
is_local = env.remote_addr in local_hosts,
is_https = env.wsgi_url_scheme \
in ['https', 'HTTPS'] or env.https=='on')
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES \
or request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
or env.https=='on')
request.uuid = request.compute_uuid() # requires client
request.url = environ['PATH_INFO']
@@ -781,7 +795,6 @@ class HttpServer(object):
global_settings.applications_parent = path
os.chdir(path)
[add_path_first(p) for p in (path, abspath('site-packages'), "")]
custom_import_install(web2py_path)
if exists("logging.conf"):
logging.config.fileConfig("logging.conf")
+7 -3
View File
@@ -314,22 +314,26 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
(action,models,command) = (True,'-M',command[1:])
else:
action=False
if action and command.endswith('.py'):
commands.extend(('-J', # cron job
models, # import models?
'-S', app, # app name
'-a', '"<recycle>"', # password
'-R', command)) # command
shell = True
elif action:
commands.extend(('-J', # cron job
models, # import models?
'-S', app+'/'+command, # app name
'-a', '"<recycle>"')) # password
shell = True
else:
commands = command
shell = False
# from python docs:
# You do not need shell=True to run a batch file or
# console-based executable.
shell = False
try:
cronlauncher(commands, shell=shell).start()
except Exception, e:
+3 -2
View File
@@ -140,8 +140,9 @@ class LockedFile(object):
self.file.close()
self.file = None
def __del__(self):
self.close()
if not self.file is None:
self.close()
def read_locked(filename):
fp = LockedFile(filename, 'r')
data = fp.read()
+129 -183
View File
@@ -33,12 +33,29 @@ exists = os.path.exists
pjoin = os.path.join
logger = logging.getLogger('web2py.rewrite')
thread = threading.local() # thread-local storage for routing params
THREAD_LOCAL = threading.local() # thread-local storage for routing params
regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
regex_anything = re.compile(r'(?<!\\)\$anything')
regex_redirect = re.compile(r'(\d+)->(.*)')
regex_full_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
regex_version = re.compile(r'^(_[\d]+\.[\d]+\.[\d]+)$')
# pattern to replace spaces with underscore in URL
# also the html escaped variants '+' and '%20' are covered
regex_space = re.compile('(\+|\s|%20)+')
# pattern to find valid paths in url /application/controller/...
# this could be:
# for static pages:
# /<b:application>/static/<x:file>
# for dynamic pages:
# /<a:application>[/<c:controller>[/<f:function>[.<e:ext>][/<s:args>]]]
# application, controller, function and ext may only contain [a-zA-Z0-9_]
# file and args may also contain '-', '=', '.' and '/'
# apps in routes_apps_raw must parse raw_args into args
regex_url = re.compile('^/((?P<a>\w+)(/(?P<c>\w+)(/(?P<z>(?P<f>\w+)(\.(?P<e>[\w.]+))?(?P<s>[/\w@=-]*(\.[/\w@=-]+)*)))?)?)?$')
def _router_default():
"return new copy of default base router"
@@ -84,7 +101,7 @@ def _params_default(app=None):
params_apps = dict()
params = _params_default(app=None) # regex rewrite parameters
thread.routes = params # default to base regex rewrite parameters
THREAD_LOCAL.routes = params # default to base regex rewrite parameters
routers = None
def log_rewrite(string):
@@ -107,16 +124,16 @@ def log_rewrite(string):
logger.debug(string)
ROUTER_KEYS = set(
('default_application', 'applications',
('default_application', 'applications',
'default_controller', 'controllers',
'default_function', 'functions',
'default_function', 'functions',
'default_language', 'languages',
'domain', 'domains', 'root_static', 'path_prefix',
'exclusive_domain', 'map_hyphen', 'map_static',
'acfe_match', 'file_match', 'args_match'))
ROUTER_BASE_KEYS = set(
('applications', 'default_application',
('applications', 'default_application',
'domains', 'path_prefix'))
# The external interface to rewrite consists of:
@@ -125,11 +142,11 @@ ROUTER_BASE_KEYS = set(
# url_in: parse and rewrite incoming URL
# url_out: assemble and rewrite outgoing URL
#
# thread.routes.default_application
# thread.routes.error_message
# thread.routes.error_message_ticket
# thread.routes.try_redirect_on_error
# thread.routes.error_handler
# THREAD_LOCAL.routes.default_application
# THREAD_LOCAL.routes.error_message
# THREAD_LOCAL.routes.error_message_ticket
# THREAD_LOCAL.routes.try_redirect_on_error
# THREAD_LOCAL.routes.error_handler
#
# filter_url: helper for doctest & unittest
# filter_err: helper for doctest & unittest
@@ -140,8 +157,8 @@ def fixup_missing_path_info(environ):
path_info = eget('PATH_INFO')
request_uri = eget('REQUEST_URI')
if not path_info and request_uri:
# for fcgi, get path_info and
# query_string from request_uri
# for fcgi, get path_info and
# query_string from request_uri
items = request_uri.split('?')
path_info = environ['PATH_INFO'] = items[0]
environ['QUERY_STRING'] = items[1] if len(items) > 1 else ''
@@ -154,45 +171,36 @@ def fixup_missing_path_info(environ):
if not eget('HTTP_HOST'):
environ['HTTP_HOST'] = \
'%s:%s' % (eget('SERVER_NAME'),eget('SERVER_PORT'))
def url_in(request, environ):
"parse and rewrite incoming URL"
if routers:
return map_url_in(request, environ)
return regex_url_in(request, environ)
def url_out(request, env, application, controller, function,
def url_out(request, environ, application, controller, function,
args, other, scheme, host, port):
"assemble and rewrite outgoing URL"
if routers:
acf = map_url_out(request, env, application, controller,
acf = map_url_out(request, environ, application, controller,
function, args, other, scheme, host, port)
url = '%s%s' % (acf, other)
else:
url = '/%s/%s/%s%s' % (application, controller, function, other)
url = regex_filter_out(url, env)
url = regex_filter_out(url, environ)
#
# fill in scheme and host if absolute URL is requested
# scheme can be a string, eg 'http', 'https', 'ws', 'wss'
#
if scheme or port is not None:
if host is None: # scheme or port implies host
host = True
if host is True or (host is None and (scheme or port!=None)):
host = request.env.http_host
if not scheme or scheme is True:
if request and request.env:
scheme = request.env.get('wsgi_url_scheme', 'http').lower()
else:
scheme = 'http' # some reasonable default in case we need it
if host is not None:
if host is True:
host = request.env.http_host
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
if request else 'http'
if host:
if port is None:
port = ''
else:
port = ':%s' % port
url = '%s://%s%s%s' % (scheme, host, port, url)
host_port = host if not port else host.split(':',1)[0]+':%s'%port
url = '%s://%s%s' % (scheme, host_port, url)
return url
def try_rewrite_on_error(http_response, request, environ, ticket=None):
@@ -200,12 +208,12 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
called from main.wsgibase to rewrite the http response.
"""
status = int(str(http_response.status).split()[0])
if status>=399 and thread.routes.routes_onerror:
if status>=399 and THREAD_LOCAL.routes.routes_onerror:
keys=set(('%s/%s' % (request.application, status),
'%s/*' % (request.application),
'*/%s' % (status),
'*/*'))
for (key,uri) in thread.routes.routes_onerror:
for (key,uri) in THREAD_LOCAL.routes.routes_onerror:
if key in keys:
if uri == '!':
# do nothing!
@@ -229,7 +237,7 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
path_info = '/' + path_info.lstrip('/') # add leading '/' if missing
environ['PATH_INFO'] = path_info
error_handling_path = \
url_in(request, environ)[1]['PATH_INFO']
url_in(request, environ)[2]['PATH_INFO']
# Avoid infinite loop.
if error_handling_path != error_raising_path:
# wsgibase will be called recursively with the routes_onerror path.
@@ -243,12 +251,12 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
def try_redirect_on_error(http_object, request, ticket=None):
"called from main.wsgibase to rewrite the http response"
status = int(str(http_object.status).split()[0])
if status>399 and thread.routes.routes_onerror:
if status>399 and THREAD_LOCAL.routes.routes_onerror:
keys=set(('%s/%s' % (request.application, status),
'%s/*' % (request.application),
'*/%s' % (status),
'*/*'))
for (key,redir) in thread.routes.routes_onerror:
for (key,redir) in THREAD_LOCAL.routes.routes_onerror:
if key in keys:
if redir == '!':
break
@@ -281,7 +289,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
global params_apps
params_apps = dict()
params = _params_default(app=None) # regex rewrite parameters
thread.routes = params # default to base regex rewrite parameters
THREAD_LOCAL.routes = params # default to base regex rewrite parameters
routers = None
if isinstance(rdict, dict):
@@ -328,7 +336,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
if app is None:
params = p # install base rewrite parameters
thread.routes = params # install default as current routes
THREAD_LOCAL.routes = params # install default as current routes
#
# create the BASE router if routers in use
#
@@ -537,24 +545,25 @@ def regex_select(env=None, app=None, request=None):
select a set of regex rewrite params for the current request
"""
if app:
thread.routes = params_apps.get(app, params)
THREAD_LOCAL.routes = params_apps.get(app, params)
elif env and params.routes_app:
if routers:
map_url_in(request, env, app=True)
else:
app = regex_uri(env, params.routes_app, "routes_app")
thread.routes = params_apps.get(app, params)
THREAD_LOCAL.routes = params_apps.get(app, params)
else:
thread.routes = params # default to base rewrite parameters
log_rewrite("select routing parameters: %s" % thread.routes.name)
THREAD_LOCAL.routes = params # default to base rewrite parameters
log_rewrite("select routing parameters: %s" % THREAD_LOCAL.routes.name)
return app # for doctest
def regex_filter_in(e):
"regex rewrite incoming URL"
routes = THREAD_LOCAL.routes
query = e.get('QUERY_STRING', None)
e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
if thread.routes.routes_in:
path = regex_uri(e, thread.routes.routes_in,
if routes.routes_in:
path = regex_uri(e, routes.routes_in,
"routes_in", e['PATH_INFO'])
rmatch = regex_redirect.match(path)
if rmatch:
@@ -570,58 +579,7 @@ def regex_filter_in(e):
e['REQUEST_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
return e
# pattern to replace spaces with underscore in URL
# also the html escaped variants '+' and '%20' are covered
regex_space = re.compile('(\+|\s|%20)+')
# pattern to find valid paths in url /application/controller/...
# this could be:
# for static pages:
# /<b:application>/static/<x:file>
# for dynamic pages:
# /<a:application>[/<c:controller>[/<f:function>[.<e:ext>][/<s:args>]]]
# application, controller, function and ext may only contain [a-zA-Z0-9_]
# file and args may also contain '-', '=', '.' and '/'
# apps in routes_apps_raw must parse raw_args into args
regex_static = re.compile(r'''
(^ # static pages
/(?P<b> \w+) # b=app
/static # /b/static
/(?P<x> (\w[\-\=\./]?)* ) # x=file
$)
''', re.X)
regex_url = re.compile(r'''
(^( # (/a/c/f.e/s)
/(?P<a> [\w\s+]+ ) # /a=app
( # (/c.f.e/s)
/(?P<c> [\w\s+]+ ) # /a/c=controller
( # (/f.e/s)
/(?P<f> [\w\s+]+ ) # /a/c/f=function
( # (.e)
\.(?P<e> [\w\s+]+ ) # /a/c/f.e=extension
)?
( # (/s)
/(?P<r> # /a/c/f.e/r=raw_args
.*
)
)?
)?
)?
)?
/?$)
''', re.X)
regex_args = re.compile(r'''
(^
(?P<s>
( [\w@/-][=.]? )* # s=args
)?
/?$) # trailing slash
''', re.X)
def sluggify(key):
return key.lower().replace('.','_')
@@ -635,72 +593,61 @@ def regex_url_in(request, environ):
# ##################################################
regex_select(env=environ, request=request)
if thread.routes.routes_in:
routes = THREAD_LOCAL.routes
if routes.routes_in:
environ = regex_filter_in(environ)
request.env.update((sluggify(k),v) for k,v in environ.iteritems())
path = request.env.path_info.replace('\\', '/')
# ##################################################
# serve if a static file
# ##################################################
match = regex_static.match(regex_space.sub('_', path))
if match and match.group('x'):
static_file = pjoin(request.env.applications_parent,
'applications', match.group('b'),
'static', match.group('x'))
return (static_file, environ)
# ##################################################
# parse application, controller and function
# ##################################################
path = re.sub('%20', ' ', path)
path = request.env.path_info.replace('\\', '/') or '/'
path = regex_space.sub('_',path)
if path.endswith('/') and len(path)>1: path = path[:-1]
match = regex_url.match(path)
if not match or match.group('c') == 'static':
if not match:
raise HTTP(400,
thread.routes.error_message % 'invalid request',
web2py_error='invalid path')
request.application = \
regex_space.sub('_', match.group('a') or thread.routes.default_application)
request.controller = \
regex_space.sub('_', match.group('c') or thread.routes.default_controller)
request.function = \
regex_space.sub('_', match.group('f') or thread.routes.default_function)
group_e = match.group('e')
request.raw_extension = group_e and regex_space.sub('_', group_e) or None
request.extension = request.raw_extension or 'html'
request.raw_args = match.group('r')
request.args = List([])
if request.application in thread.routes.routes_apps_raw:
# application is responsible for parsing args
request.args = None
elif request.raw_args:
match = regex_args.match(request.raw_args.replace(' ', '_'))
if match:
group_s = match.group('s')
request.args = \
List((group_s and group_s.split('/')) or [])
if request.args and request.args[-1] == '':
request.args.pop() # adjust for trailing empty arg
routes.error_message % 'invalid request',
web2py_error='invalid path')
elif match.group('c')=='static':
application = match.group('a')
version, filename = None, match.group('z')
items = filename.split('/',1)
if regex_version.match(items[0]):
version,filename = items
static_file = pjoin(request.env.applications_parent,
'applications', application,
'static', filename)
return (static_file, version, environ)
else:
# ##################################################
# parse application, controller and function
# ##################################################
request.application = match.group('a') or routes.default_application
request.controller = match.group('c') or routes.default_controller
request.function = match.group('f') or routes.default_function
request.raw_extension = match.group('e')
request.extension = request.raw_extension or 'html'
request.raw_args = match.group('s')
if request.application in routes.routes_apps_raw:
# application is responsible for parsing args
request.args = None
elif request.raw_args:
request.args = List(request.raw_args.split('/')[1:])
else:
raise HTTP(400,
thread.routes.error_message % 'invalid request',
web2py_error='invalid path (args)')
return (None, environ)
request.args = List([])
return (None, None, environ)
def regex_filter_out(url, e=None):
"regex rewrite outgoing URL"
if not hasattr(thread, 'routes'):
regex_select() # ensure thread.routes is set (for application threads)
if not hasattr(THREAD_LOCAL, 'routes'):
regex_select() # ensure routes is set (for application threads)
routes = THREAD_LOCAL.routes
if routers:
return url # already filtered
if thread.routes.routes_out:
if routes.routes_out:
items = url.split('?', 1)
if e:
host = e.get('http_host', 'localhost').lower()
@@ -713,7 +660,7 @@ def regex_filter_out(url, e=None):
e.get('request_method', 'get').lower(), items[0])
else:
items[0] = ':http://localhost:get %s' % items[0]
for (regex, value, tmp) in thread.routes.routes_out:
for (regex, value, tmp) in routes.routes_out:
if regex.match(items[0]):
rewritten = '?'.join([regex.sub(value, items[0])] + items[1:])
log_rewrite('routes_out: [%s] -> %s' % (url, rewritten))
@@ -722,9 +669,9 @@ def regex_filter_out(url, e=None):
return url
def filter_url(url, method='get', remote='0.0.0.0',
def filter_url(url, method='get', remote='0.0.0.0',
out=False, app=False, lang=None,
domain=(None,None), env=False, scheme=None,
domain=(None,None), env=False, scheme=None,
host=None, port=None):
"""
doctest/unittest interface to regex_filter_in() and regex_filter_out()
@@ -793,7 +740,7 @@ def filter_url(url, method='get', remote='0.0.0.0',
# rewrite inbound URL
#
(static, e) = url_in(request, e)
(static, version, e) = url_in(request, e)
if static:
return static
result = "/%s/%s/%s" % (request.application, request.controller, request.function)
@@ -812,12 +759,13 @@ def filter_url(url, method='get', remote='0.0.0.0',
def filter_err(status, application='app', ticket='tkt'):
"doctest/unittest interface to routes_onerror"
if status > 399 and thread.routes.routes_onerror:
routes = THREAD_LOCAL.routes
if status > 399 and routes.routes_onerror:
keys = set(('%s/%s' % (application, status),
'%s/*' % (application),
'*/%s' % (status),
'*/*'))
for (key,redir) in thread.routes.routes_onerror:
for (key,redir) in routes.routes_onerror:
if key in keys:
if redir == '!':
break
@@ -921,12 +869,12 @@ class MapUrlIn(object):
self.pop_arg_if(self.application == arg0)
if not base._acfe_match.match(self.application):
raise HTTP(400, thread.routes.error_message % 'invalid request',
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error="invalid application: '%s'" % self.application)
if self.application not in routers and \
(self.application != thread.routes.default_application or self.application == 'welcome'):
raise HTTP(400, thread.routes.error_message % 'invalid request',
(self.application != THREAD_LOCAL.routes.default_application or self.application == 'welcome'):
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error="unknown application: '%s'" % self.application)
# set the application router
@@ -956,14 +904,15 @@ class MapUrlIn(object):
a root-static file is one whose incoming URL expects it to be at the root,
typically robots.txt & favicon.ico
'''
if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static'
root_static_file = pjoin(self.request.env.applications_parent,
'applications', self.application,
self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file)
return root_static_file
return None
return root_static_file, None
return None, None
def map_language(self):
"handle language (no hyphen mapping)"
@@ -989,7 +938,7 @@ class MapUrlIn(object):
self.pop_arg_if(arg0 == self.controller)
log_rewrite("route: controller=%s" % self.controller)
if not self.router._acfe_match.match(self.controller):
raise HTTP(400, thread.routes.error_message % 'invalid request',
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid controller')
def map_static(self):
@@ -998,8 +947,12 @@ class MapUrlIn(object):
file_match but no hyphen mapping
'''
if self.controller != 'static':
return None
file = '/'.join(self.args)
return None, None
version = regex_version.match(self.args(0))
if self.args and version:
file = '/'.join(self.args[1:])
else:
file = '/'.join(self.args)
if len(self.args) == 0:
bad_static = True # require a file name
elif '/' in self.file_match:
@@ -1013,7 +966,7 @@ class MapUrlIn(object):
if bad_static:
log_rewrite('bad static path=%s' % file)
raise HTTP(400,
thread.routes.error_message % 'invalid request',
THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid static file')
#
# support language-specific static subdirectories,
@@ -1030,7 +983,7 @@ class MapUrlIn(object):
'static', file)
self.extension = None
log_rewrite("route: static=%s" % static_file)
return static_file
return static_file, version
def map_function(self):
"handle function.extension"
@@ -1055,10 +1008,10 @@ class MapUrlIn(object):
log_rewrite("route: function.ext=%s.%s" % (self.function, self.extension))
if not self.router._acfe_match.match(self.function):
raise HTTP(400, thread.routes.error_message % 'invalid request',
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid function')
if self.extension and not self.router._acfe_match.match(self.extension):
raise HTTP(400, thread.routes.error_message % 'invalid request',
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid extension')
def validate_args(self):
@@ -1067,14 +1020,14 @@ class MapUrlIn(object):
'''
for arg in self.args:
if not self.router._args_match.match(arg):
raise HTTP(400, thread.routes.error_message % 'invalid request',
raise HTTP(400, THREAD_LOCAL.routes.error_message % 'invalid request',
web2py_error='invalid arg <%s>' % arg)
def sluggify(self):
""
self.request.env.update(
(sluggify(k),v) for k,v in self.env.iteritems())
def update_request(self):
'''
update request from self
@@ -1098,7 +1051,7 @@ class MapUrlIn(object):
if self.language:
uri = '/%s%s' % (self.language, uri)
uri = '/%s%s%s%s' % (
app,
app,
uri,
urllib.quote('/'+'/'.join(str(x) for x in self.args)) if self.args else '',
('?' + self.query) if self.query else '')
@@ -1292,24 +1245,24 @@ def map_url_in(request, env, app=False):
# initialize router-url object
#
thread.routes = params # default to base routes
THREAD_LOCAL.routes = params # default to base routes
map = MapUrlIn(request=request, env=env)
map.sluggify()
map.map_prefix() # strip prefix if present
map.map_app() # determine application
# configure thread.routes for error rewrite
# configure THREAD_LOCAL.routes for error rewrite
#
if params.routes_app:
thread.routes = params_apps.get(app, params)
THREAD_LOCAL.routes = params_apps.get(app, params)
if app:
return map.application
root_static_file = map.map_root_static() # handle root-static files
root_static_file, version = map.map_root_static() # handle root-static files
if root_static_file:
map.update_request()
return (root_static_file, map.env)
return (root_static_file, version, map.env)
# handle mapping of lang/static to static/lang in externally-rewritten URLs
# in case we have to handle them ourselves
if map.languages and map.map_static is False and map.arg0 == 'static' and map.args(1) in map.languages:
@@ -1318,14 +1271,14 @@ def map_url_in(request, env, app=False):
else:
map.map_language()
map.map_controller()
static_file = map.map_static()
static_file, version = map.map_static()
if static_file:
map.update_request()
return (static_file, map.env)
return (static_file, version, map.env)
map.map_function()
map.validate_args()
map.update_request()
return (None, map.env)
return (None, None, map.env)
def map_url_out(request, env, application, controller,
function, args, other, scheme, host, port):
@@ -1363,10 +1316,3 @@ def get_effective_router(appname):
if not routers or appname not in routers:
return None
return Storage(routers[appname]) # return a copy
+16 -222
View File
@@ -2,6 +2,7 @@
# This file is part of the Rocket Web Server
# Copyright (c) 2011 Timothy Farrell
# Modified by Massimo Di Pierro
# Import System Modules
import sys
@@ -12,7 +13,7 @@ import platform
import traceback
# Define Constants
VERSION = '1.2.4'
VERSION = '1.2.5'
SERVER_NAME = socket.gethostname()
SERVER_SOFTWARE = 'Rocket %s' % VERSION
HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0])
@@ -1454,41 +1455,34 @@ class Worker(Thread):
return req
def read_headers(self, sock_file):
def read_headers(self, sock_file, environ):
try:
headers = dict()
l = sock_file.readline()
lname = None
lval = None
while True:
l = sock_file.readline()
if PY3K:
try:
l = str(l, 'ISO-8859-1')
except UnicodeDecodeError:
self.err_log.warning('Client sent invalid header: ' + repr(l))
self.err_log.warning('Invalid request header: '+repr(l))
if l == '\r\n':
if l.strip().replace('\0','') == '':
break
if l[0] in ' \t' and lname:
elif l[0] in ' \t' and lname:
# Some headers take more than one line
lval += ',' + l.strip()
environ[lname] += ' ' + l.strip()
else:
# HTTP header values are latin-1 encoded
l = l.split(':', 1)
# HTTP header names are us-ascii encoded
lname = l[0].strip().upper().replace('-', '_')
lval = l[-1].strip()
headers[str(lname)] = str(lval)
lname = str('HTTP_'+l[0].strip().upper().replace('-', '_'))
lval = str(l[-1].strip())
environ[lname] = lval
l = sock_file.readline()
except socket.timeout:
raise SocketTimeout("Socket timed out before request.")
return headers
class SocketTimeout(Exception):
"Exception for when a socket times out between requests."
pass
@@ -1549,209 +1543,10 @@ class ChunkedReader(object):
yield self.readline()
def get_method(method):
methods = dict(wsgi=WSGIWorker,
fs=FileSystemWorker)
methods = dict(wsgi=WSGIWorker)
return methods[method.lower()]
# Monolithic build...end of module: rocket\worker.py
# Monolithic build...start of module: rocket\methods\__init__.py
# Monolithic build...end of module: rocket\methods\__init__.py
# Monolithic build...start of module: rocket\methods\fs.py
# Import System Modules
import os
import time
import mimetypes
from email.utils import formatdate
from wsgiref.headers import Headers
from wsgiref.util import FileWrapper
# Import Package Modules
# package imports removed in monolithic build
# Define Constants
CHUNK_SIZE = 2**16 # 64 Kilobyte chunks
HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s'''
INDEX_HEADER = '''\
<html>
<head><title>Directory Index: %(path)s</title>
<style> .parent { margin-bottom: 1em; }</style>
</head>
<body><h1>Directory Index: %(path)s</h1>
<table>
<tr><th>Directories</th></tr>
'''
INDEX_ROW = '''<tr><td><div class="%(cls)s"><a href="/%(link)s">%(name)s</a></div></td></tr>'''
INDEX_FOOTER = '''</table></body></html>\r\n'''
class LimitingFileWrapper(FileWrapper):
def __init__(self, limit=None, *args, **kwargs):
self.limit = limit
FileWrapper.__init__(self, *args, **kwargs)
def read(self, amt):
if amt > self.limit:
amt = self.limit
self.limit -= amt
return FileWrapper.read(self, amt)
class FileSystemWorker(Worker):
def __init__(self, *args, **kwargs):
"""Builds some instance variables that will last the life of the
thread."""
Worker.__init__(self, *args, **kwargs)
self.root = os.path.abspath(self.app_info['document_root'])
self.display_index = self.app_info['display_index']
def serve_file(self, filepath, headers):
filestat = os.stat(filepath)
self.size = filestat.st_size
modtime = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(filestat.st_mtime))
self.headers.add_header('Last-Modified', modtime)
if headers.get('if_modified_since') == modtime:
# The browser cache is up-to-date, send a 304.
self.status = "304 Not Modified"
self.data = []
return
ct = mimetypes.guess_type(filepath)[0]
self.content_type = ct if ct else 'text/plain'
try:
f = open(filepath, 'rb')
self.headers['Pragma'] = 'cache'
self.headers['Cache-Control'] = 'private'
self.headers['Content-Length'] = str(self.size)
if self.etag:
self.headers.add_header('Etag', self.etag)
if self.expires:
self.headers.add_header('Expires', self.expires)
try:
# Implement 206 partial file support.
start, end = headers['range'].split('-')
start = 0 if not start.isdigit() else int(start)
end = self.size if not end.isdigit() else int(end)
if self.size < end or start < 0:
self.status = "214 Unsatisfiable Range Requested"
self.data = FileWrapper(f, CHUNK_SIZE)
else:
f.seek(start)
self.data = LimitingFileWrapper(f, CHUNK_SIZE, limit=end)
self.status = "206 Partial Content"
except:
self.data = FileWrapper(f, CHUNK_SIZE)
except IOError:
self.status = "403 Forbidden"
def serve_dir(self, pth, rpth):
def rel_path(path):
return os.path.normpath(path[len(self.root):] if path.startswith(self.root) else path)
if not self.display_index:
self.status = '404 File Not Found'
return b('')
else:
self.content_type = 'text/html'
dir_contents = [os.path.join(pth, x) for x in os.listdir(os.path.normpath(pth))]
dir_contents.sort()
dirs = [rel_path(x)+'/' for x in dir_contents if os.path.isdir(x)]
files = [rel_path(x) for x in dir_contents if os.path.isfile(x)]
self.data = [INDEX_HEADER % dict(path='/'+rpth)]
if rpth:
self.data += [INDEX_ROW % dict(name='(parent directory)', cls='dir parent', link='/'.join(rpth[:-1].split('/')[:-1]))]
self.data += [INDEX_ROW % dict(name=os.path.basename(x[:-1]), link=os.path.join(rpth, os.path.basename(x[:-1])).replace('\\', '/'), cls='dir') for x in dirs]
self.data += ['<tr><th>Files</th></tr>']
self.data += [INDEX_ROW % dict(name=os.path.basename(x), link=os.path.join(rpth, os.path.basename(x)).replace('\\', '/'), cls='file') for x in files]
self.data += [INDEX_FOOTER]
self.headers['Content-Length'] = self.size = str(sum([len(x) for x in self.data]))
self.status = '200 OK'
def run_app(self, conn):
self.status = "200 OK"
self.size = 0
self.expires = None
self.etag = None
self.content_type = 'text/plain'
self.content_length = None
if __debug__:
self.err_log.debug('Getting sock_file')
# Build our file-like object
sock_file = conn.makefile('rb',BUF_SIZE)
request = self.read_request_line(sock_file)
if request['method'].upper() not in ('GET', ):
self.status = "501 Not Implemented"
try:
# Get our file path
reader = self.read_headers(sock_file)
headers = dict((k.lower(),v) for k,v in reader.iteritems())
rpath = request.get('path', '').lstrip('/')
filepath = os.path.join(self.root, rpath)
filepath = os.path.abspath(filepath)
if __debug__:
self.err_log.debug('Request for path: %s' % filepath)
self.closeConnection = headers.get('connection', 'close').lower() == 'close'
self.headers = Headers([('Date', formatdate(usegmt=True)),
('Server', HTTP_SERVER_SOFTWARE),
('Connection', headers.get('connection', 'close')),
])
if not filepath.lower().startswith(self.root.lower()):
# File must be within our root directory
self.status = "400 Bad Request"
self.closeConnection = True
elif not os.path.exists(filepath):
self.status = "404 File Not Found"
self.closeConnection = True
elif os.path.isdir(filepath):
self.serve_dir(filepath, rpath)
elif os.path.isfile(filepath):
self.serve_file(filepath, headers)
else:
# It exists but it's not a file or a directory????
# What is it then?
self.status = "501 Not Implemented"
self.closeConnection = True
h = self.headers
statcode, statstr = self.status.split(' ', 1)
statcode = int(statcode)
if statcode >= 400:
h.add_header('Content-Type', self.content_type)
self.data = [statstr]
# Build our output headers
header_data = HEADER_RESPONSE % (self.status, str(h))
# Send the headers
if __debug__:
self.err_log.debug('Sending Headers: %s' % repr(header_data))
self.conn.sendall(b(header_data))
for data in self.data:
self.conn.sendall(b(data))
if hasattr(self.data, 'close'):
self.data.close()
finally:
if __debug__:
self.err_log.debug('Finally closing sock_file')
sock_file.close()
# Monolithic build...end of module: rocket\methods\fs.py
# Monolithic build...start of module: rocket\methods\wsgi.py
# Import System Modules
@@ -1819,16 +1614,15 @@ class WSGIWorker(Worker):
environ = self.base_environ.copy()
# Grab the headers
for k, v in self.read_headers(sock_file).items():
environ[str('HTTP_'+k)] = v
self.read_headers(sock_file,environ)
# Add CGI Variables
environ['REQUEST_METHOD'] = request['method']
environ['PATH_INFO'] = request['path']
environ['SERVER_PROTOCOL'] = request['protocol']
environ['SERVER_PORT'] = str(conn.server_port)
environ['REMOTE_PORT'] = str(conn.client_port)
environ['REMOTE_ADDR'] = str(conn.client_addr)
environ['REQUEST_METHOD'] = request['method']
environ['PATH_INFO'] = request['path']
environ['SERVER_PROTOCOL'] = request['protocol']
environ['QUERY_STRING'] = request['query_string']
if 'HTTP_CONTENT_LENGTH' in environ:
environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH']
+4 -3
View File
@@ -88,7 +88,7 @@ except:
from simplejson import loads, dumps
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB, IS_INT_IN_RANGE, IS_DATETIME
from gluon.utils import web2py_uuid
@@ -443,7 +443,7 @@ class Scheduler(MetaScheduler):
Field('application_name',requires=IS_NOT_EMPTY(),
default=None,writable=False),
Field('task_name',default=None),
Field('group_name',default='main',writable=False),
Field('group_name',default='main'),
Field('status',requires=IS_IN_SET(TASK_STATUS),
default=QUEUED,writable=False),
Field('function_name',
@@ -454,7 +454,8 @@ class Scheduler(MetaScheduler):
Field('args','text',default='[]',requires=TYPE(list)),
Field('vars','text',default='{}',requires=TYPE(dict)),
Field('enabled','boolean',default=True),
Field('start_time','datetime',default=now, requires=IS_NOT_EMPTY()),
Field('start_time','datetime',default=now,
requires = IS_DATETIME()),
Field('next_run_time','datetime',default=now),
Field('stop_time','datetime'),
Field('repeats','integer',default=1,comment="0=unlimited",
+1 -1
View File
@@ -6,6 +6,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
import os
import sys
import socket
import platform
from storage import Storage
@@ -37,4 +38,3 @@ global_settings.is_jython = \
str(sys.copyright).find('Jython') > 0
+2 -3
View File
@@ -19,7 +19,7 @@ import optparse
import glob
import traceback
import fileutils
import settings
from settings import global_settings
from utils import web2py_uuid
from compileapp import build_environment, read_pyc, run_models_in
from restricted import RestrictedError
@@ -28,7 +28,6 @@ from storage import Storage
from admin import w2p_unpack
from dal import BaseAdapter
logger = logging.getLogger("web2py")
def exec_environment(
@@ -112,7 +111,7 @@ def env(
request.env.path_info = '/%s/%s/%s' % (a, c, f)
request.env.http_host = '127.0.0.1:8000'
request.env.remote_addr = '127.0.0.1'
request.env.web2py_runtime_gae = settings.global_settings.web2py_runtime_gae
request.env.web2py_runtime_gae = global_settings.web2py_runtime_gae
for k,v in extra_request.items():
request[k] = v
+100 -56
View File
@@ -28,7 +28,7 @@ from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
from storage import Storage
from utils import md5_hash
from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG
import datetime
import urllib
@@ -174,8 +174,7 @@ class TextWidget(FormWidget):
"""
default = dict(value = value)
attr = cls._attributes(field, default,
**attributes)
attr = cls._attributes(field, default,**attributes)
return TEXTAREA(**attr)
@@ -239,11 +238,14 @@ class ListWidget(StringWidget):
_name = field.name
if field.type=='list:integer': _class = 'integer'
else: _class = 'string'
requires = field.requires if isinstance(field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None
requires = field.requires if isinstance(
field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None
attributes['_style'] = 'list-style:none'
items=[LI(INPUT(_id=_id, _class=_class, _name=_name,
value=v, hideerror=True, requires=requires),
**attributes) for v in value or ['']]
nvalue = value or ['']
items = [LI(INPUT(_id=_id, _class=_class, _name=_name,
value=v, hideerror=k<len(nvalue)-1,
requires=requires),
**attributes) for (k,v) in enumerate(nvalue)]
script=SCRIPT("""
// from http://refactormycode.com/codes/694-expanding-input-list-using-jquery
(function(){
@@ -377,12 +379,11 @@ class CheckboxesWidget(OptionsWidget):
requires = field.requires
if not isinstance(requires, (list, tuple)):
requires = [requires]
if requires:
if hasattr(requires[0], 'options'):
options = requires[0].options()
else:
raise SyntaxError, 'widget cannot determine options of %s' \
% field
if requires and hasattr(requires[0], 'options'):
options = requires[0].options()
else:
raise SyntaxError, 'widget cannot determine options of %s' \
% field
options = [(k, v) for k, v in options if k != '']
opts = []
@@ -439,14 +440,23 @@ class PasswordWidget(FormWidget):
see also: :meth:`FormWidget.widget`
"""
# detect if attached a IS_STRONG with entropy
default=dict(
_type='password',
_value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '',
)
attr = cls._attributes(field, default, **attributes)
output = CAT(INPUT(**attr))
return INPUT(**attr)
# deal with entropy check!
requires = field.requires
if not isinstance(requires,(list,tuple)): requires = [requires]
is_strong = [r for r in requires if isinstance(r, IS_STRONG)]
if is_strong:
output.append(SCRIPT("web2py_validate_entropy(jQuery('#%s'),%s);" \
% (attr['_id'],is_strong[0].entropy)))
# end entropy check
return output
class UploadWidget(FormWidget):
@@ -679,6 +689,16 @@ def formstyle_divs(form, fields):
table.append(DIV(_label, _controls, _help, _id=id))
return table
def formstyle_inline(form, fields):
''' divs only '''
if len(fields)!=2:
raise RuntimeError, "Not possible"
id, label, controls, help = fields[0]
submit_button = fields[1][2]
return CAT(DIV(controls,_style='display:inline'),
submit_button)
def formstyle_ul(form, fields):
''' unordered list '''
table = UL()
@@ -804,6 +824,7 @@ class SQLFORM(FORM):
divs = formstyle_divs,
ul = formstyle_ul,
bootstrap = formstyle_bootstrap,
inline = formstyle_inline,
))
FIELDNAME_REQUEST_DELETE = 'delete_this_record'
@@ -865,6 +886,8 @@ class SQLFORM(FORM):
self.ignore_rw = ignore_rw
self.formstyle = formstyle
self.readonly = readonly
# Default dbio setting
self.detect_record_change = None
nbsp = XML('&nbsp;') # Firefox2 does not display fields with blanks
FORM.__init__(self, *[], **attributes)
@@ -998,11 +1021,11 @@ class SQLFORM(FORM):
else:
inp = field.formatter(default)
elif field.type == 'upload':
if hasattr(field, 'widget') and field.widget:
if field.widget:
inp = field.widget(field, default, upload)
else:
inp = self.widgets.upload.widget(field, default, upload)
elif hasattr(field, 'widget') and field.widget:
elif field.widget:
inp = field.widget(field, default)
elif field.type == 'boolean':
inp = self.widgets.boolean.widget(field, default)
@@ -1036,7 +1059,7 @@ class SQLFORM(FORM):
xfields.append((row_id,label,inp,comment))
self.custom.dspval[fieldname] = dspval or nbsp
self.custom.inpval[fieldname] = inpval or ''
self.custom.inpval[fieldname] = inpval if not inpval is None else ''
self.custom.widget[fieldname] = inp
# if a record is provided and found, as is linkto
@@ -1118,16 +1141,16 @@ class SQLFORM(FORM):
if defaults and len(args) - len(defaults) == 4 or len(args) == 4:
table = TABLE()
for id,a,b,c in xfields:
raw_b = self.field_parent[id] = b
newrows = formstyle(id,a,raw_b,c)
newrows = formstyle(id,a,b,c)
self.field_parent[id] = getattr(b,'parent',None)
if type(newrows).__name__ != "tuple":
newrows = [newrows]
for newrow in newrows:
table.append(newrow)
else:
for id,a,b,c in xfields:
self.field_parent[id] = b
table = formstyle(self, xfields)
for id,a,b,c in xfields:
self.field_parent[id] = getattr(b,'parent',None)
else:
raise RuntimeError, 'formstyle not supported'
return table
@@ -1142,6 +1165,7 @@ class SQLFORM(FORM):
dbio=True,
hideerror=False,
detect_record_change=False,
**kwargs
):
"""
@@ -1166,7 +1190,8 @@ class SQLFORM(FORM):
# implement logic to detect whether record exist but has been modified
# server side
self.record_changed = None
if detect_record_change:
self.detect_record_change = detect_record_change
if self.detect_record_change:
if self.record:
self.record_changed = False
serialized = '|'.join(str(self.record[k]) for k in self.table.fields())
@@ -1222,6 +1247,7 @@ class SQLFORM(FORM):
keepvalues,
onvalidation,
hideerror=hideerror,
**kwargs
)
self.deleted = \
@@ -1231,31 +1257,41 @@ class SQLFORM(FORM):
auch = record_id and self.errors and self.deleted
# auch is true when user tries to delete a record
# that does not pass validation, yet it should be deleted
if not ret and not auch:
if self.record_changed and self.detect_record_change:
message_onchange = \
kwargs.setdefault("message_onchange",
current.T("A record change was detected. " +
"Consecutive update self-submissions " +
"are not allowed. Try re-submitting or " +
"refreshing the form page."))
if message_onchange is not None:
current.response.flash = message_onchange
return ret
elif (not ret) and (not auch):
# auch is true when user tries to delete a record
# that does not pass validation, yet it should be deleted
for fieldname in self.fields:
field = self.table[fieldname]
### this is a workaround! widgets should always have default not None!
if not field.widget and field.type.startswith('list:') and \
not OptionsWidget.has_options(field):
field.widget = self.widgets.list.widget
if hasattr(field, 'widget') and field.widget and fieldname in request_vars:
if field.widget and fieldname in request_vars:
if fieldname in self.vars:
value = self.vars[fieldname]
elif self.record:
value = self.record[fieldname]
else:
value = self.table[fieldname].default
if field.type.startswith('list:') and \
isinstance(value, str):
if field.type.startswith('list:') and isinstance(value, str):
value = [value]
row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX)
widget = field.widget(field, value)
self.field_parent[row_id].components = [ widget ]
self.field_parent[row_id]._traverse(False, hideerror)
self.custom.widget[ fieldname ] = widget
parent = self.field_parent[row_id]
if parent:
parent.components = [ widget ]
parent._traverse(False, hideerror)
self.custom.widget[fieldname] = widget
self.accepted = ret
return ret
@@ -1492,15 +1528,20 @@ class SQLFORM(FORM):
'datetime':['=','!=','<','>','<=','>='],
'integer':['=','!=','<','>','<=','>='],
'double':['=','!=','<','>','<=','>='],
'id':['=','!=','<','>','<=','>='],
'reference':['=','!=','<','>','<=','>='],
'boolean':['=','!=']}
if fields[0]._db._adapter.dbengine=='google:datastore':
search_options['string'] = ['=','!=','<','>','<=','>=']
search_options['text'] = ['=','!=','<','>','<=','>=']
search_options['list:string'] = ['contains']
search_options['list:integer'] = ['contains']
search_options['list:reference'] = ['contains']
criteria = []
selectfields = []
for field in fields:
name = str(field).replace('.','-')
options = search_options.get(field.type,None)
options = search_options.get(field.type.split(' ')[0],None)
if options:
label = isinstance(field.label,str) and T(field.label) or field.label
selectfields.append(OPTION(label, _value=str(field)))
@@ -1789,16 +1830,19 @@ class SQLFORM(FORM):
table = db[request.args[-2]]
record = table(request.args[-1]) or redirect(URL('error'))
sqlformargs.update(editargs)
update_form = SQLFORM(table, record, upload=upload, ignore_rw=ignore_rw,
formstyle=formstyle, deletable=deletable,
_class='web2py_form',
submit_button=T('Submit'),
delete_label=T('Check to delete'),
**sqlformargs)
update_form.process(formname=formname,
onvalidation=onvalidation,
onsuccess=onupdate,
next=referrer)
update_form = SQLFORM(
table,
record, upload=upload, ignore_rw=ignore_rw,
formstyle=formstyle, deletable=deletable,
_class='web2py_form',
submit_button=T('Submit'),
delete_label=T('Check to delete'),
**sqlformargs)
update_form.process(
formname=formname,
onvalidation=onvalidation,
onsuccess=onupdate,
next=referrer)
res = DIV(buttons(view=details, record=record),
update_form, formfooter, _class=_class)
res.create_form = create_form
@@ -1852,7 +1896,7 @@ class SQLFORM(FORM):
rows = dbset.select(cacheable=True)
else:
rows = dbset.select(left=left,orderby=orderby,
cacheable=True*columns)
cacheable=True,*columns)
if export_type in exportManager:
value = exportManager[export_type]
@@ -1890,7 +1934,7 @@ class SQLFORM(FORM):
search_widget = search_widget[tablename]
if search_widget=='default':
search_menu = SQLFORM.search_menu(sfields)
search_widget = lambda sfield, url: CAT(add,FORM(
search_widget = lambda sfield, url: CAT(FORM(
INPUT(_name='keywords',_value=request.vars.keywords,
_id='web2py_keywords',_onfocus="jQuery('#w2p_query_fields').change();jQuery('#w2p_query_panel').slideDown();"),
INPUT(_type='submit',_value=T('Search'),_class="btn"),
@@ -1898,6 +1942,7 @@ class SQLFORM(FORM):
_onclick="jQuery('#web2py_keywords').val('');"),
_method="GET",_action=url),search_menu)
form = search_widget and search_widget(sfields,url()) or ''
console.append(add)
console.append(form)
keywords = request.vars.get('keywords','')
try:
@@ -1942,8 +1987,7 @@ class SQLFORM(FORM):
if columns and not str(field) in columns: continue
if not field.readable: continue
key = str(field)
header = headers.get(str(field),
hasattr(field,'label') and field.label or key)
header = headers.get(str(field), field.label or key)
if sortable:
if key == order:
key, marker = '~'+order, sorter_icons[0]
@@ -2003,7 +2047,7 @@ class SQLFORM(FORM):
table_fields = [f for f in fields if f._tablename in tablenames]
rows = dbset.select(left=left,orderby=orderby,
groupby=groupby,limitby=limitby,
cacheable=True,*table_fields)
*table_fields)
except SyntaxError:
rows = None
error = T("Query Not Supported")
@@ -2226,7 +2270,9 @@ class SQLFORM(FORM):
else:
break
if nargs>len(args)+1:
query = (field == id)
query = (field == id)
if isinstance(linked_tables,dict):
linked_tables = linked_tables.get(table._tablename,[])
if linked_tables is None or referee in linked_tables:
field.represent = lambda id,r=None,referee=referee,rep=field.represent: A(callable(rep) and rep(id) or id,_class=trap_class(),_href=url(args=['view',referee,id]))
except (KeyError,ValueError,TypeError):
@@ -2251,6 +2297,8 @@ class SQLFORM(FORM):
if rfield.readable:
check[rfield.tablename] = \
check.get(rfield.tablename,[])+[rfield.name]
if isinstance(linked_tables,dict):
linked_tables = linked_tables.get(table._tablename,[])
for tablename in sorted(check):
linked_fieldnames = check[tablename]
tb = db[tablename]
@@ -2366,6 +2414,7 @@ class SQLTABLE(TABLE):
extracolumns=None,
selectid=None,
renderstyle=False,
cid=None,
**attributes
):
@@ -2403,7 +2452,7 @@ class SQLTABLE(TABLE):
row.append(TH(coldict['label'],**attrcol))
elif orderby:
row.append(TH(A(headers.get(c, c),
_href=th_link+'?orderby=' + c)))
_href=th_link+'?orderby=' + c, cid=cid)))
else:
row.append(TH(headers.get(c, c)))
@@ -2698,8 +2747,3 @@ class ExporterXML(ExportClass):
out.write('</row>\n')
out.write('</rows>')
return str(out.getvalue())
+6 -19
View File
@@ -38,30 +38,17 @@ class Storage(dict):
>>> print o.a
None
"""
__slots__=()
__slots__=()
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
__getitem__ = dict.get
__getattr__ = dict.get
__repr__ = lambda self: '<Storage %s>' % dict.__repr__(self)
__getstate__ = dict
__setstate__ = dict.update
# def __getattr__(self, key):
# return dict.get(self, key, None)
# def __setattr__(self, key, value):
# self[key] = value
# def __getitem__(self, key):
# return dict.get(self, key, None)
# def __delattr__(self, key):
# del self[key]
# def __repr__(self):
# return '<Storage %s>' % dict.__repr__(self)
# def __getstate__(self):
# return dict(self)
# def __setstate__(self,values):
# self.update(values)
# http://stackoverflow.com/questions/5247250/why-does-pickle-getstate-accept-as-a-return-value-the-very-instance-it-requi
__getstate__ = lambda self: None
__copy__ = lambda self: Storage(self)
def getlist(self,key):
"""
Return a Storage value as a list.
+39 -34
View File
@@ -43,10 +43,11 @@ def stream_file_or_304_or_206(
chunk_size = DEFAULT_CHUNK_SIZE,
request = None,
headers = {},
status = 200,
error_message = None,
):
if error_message is None:
error_message = rewrite.thread.routes.error_message % 'invalid request'
error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
try:
fp = open(static_file)
except IOError, e:
@@ -60,56 +61,60 @@ def stream_file_or_304_or_206(
fp.close()
stat_file = os.stat(static_file)
fsize = stat_file[stat.ST_SIZE]
mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
time.gmtime(stat_file[stat.ST_MTIME]))
modified = stat_file[stat.ST_MTIME]
mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT',time.gmtime(modified))
headers.setdefault('Content-Type', contenttype(static_file))
headers.setdefault('Last-Modified', mtime)
headers.setdefault('Pragma', 'cache')
headers.setdefault('Cache-Control', 'private')
if request and request.env.http_if_modified_since == mtime:
raise HTTP(304, **{'Content-Type': headers['Content-Type']})
# if this is a normal response and not a respnse to an error page
if status == 200:
if request and request.env.http_if_modified_since == mtime:
raise HTTP(304, **{'Content-Type': headers['Content-Type']})
elif request and request.env.http_range:
start_items = regex_start_range.findall(request.env.http_range)
if not start_items:
start_items = [0]
stop_items = regex_stop_range.findall(request.env.http_range)
if not stop_items or int(stop_items[0]) > fsize - 1:
stop_items = [fsize - 1]
part = (int(start_items[0]), int(stop_items[0]), fsize)
bytes = part[1] - part[0] + 1
try:
stream = open(static_file, 'rb')
except IOError, e:
if e[0] in (errno.EISDIR, errno.EACCES):
raise HTTP(403)
else:
raise HTTP(404)
stream.seek(part[0])
headers['Content-Range'] = 'bytes %i-%i/%i' % part
headers['Content-Length'] = '%i' % bytes
status = 206
else:
elif request and request.env.http_range:
start_items = regex_start_range.findall(request.env.http_range)
if not start_items:
start_items = [0]
stop_items = regex_stop_range.findall(request.env.http_range)
if not stop_items or int(stop_items[0]) > fsize - 1:
stop_items = [fsize - 1]
part = (int(start_items[0]), int(stop_items[0]), fsize)
bytes = part[1] - part[0] + 1
try:
stream = open(static_file, 'rb')
except IOError, e:
if e[0] in (errno.EISDIR, errno.EACCES):
raise HTTP(403)
else:
raise HTTP(404)
stream.seek(part[0])
headers['Content-Range'] = 'bytes %i-%i/%i' % part
headers['Content-Length'] = '%i' % bytes
status = 206
# in all the other cases (not 304, not 206, but 200 or error page)
if status != 206:
enc = request.env.http_accept_encoding
if enc and 'gzip' in enc and not 'Content-Encoding' in headers:
gzipped = static_file + '.gz'
if os.path.isfile(gzipped) and os.path.getmtime(gzipped)>modified:
static_file = gzipped
fsize = os.path.getsize(gzipped)
headers['Content-Encoding'] = 'gzip'
headers['Vary'] = 'Accept-Encoding'
try:
stream = open(static_file, 'rb')
except IOError, e:
# this better does not happer when returning an error page ;-)
if e[0] in (errno.EISDIR, errno.EACCES):
raise HTTP(403)
else:
raise HTTP(404)
headers['Content-Length'] = fsize
bytes = None
status = 200
if request and request.env.web2py_use_wsgi_file_wrapper:
wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
else:
wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes)
raise HTTP(status, wrapped, **headers)
+1 -1
View File
@@ -416,7 +416,7 @@ class TemplateParser(object):
# Allow Views to include other views dynamically
context = self.context
if current and not "response" in context:
context["response"] = current.response
context["response"] = getattr(current,'response',None)
# Get the filename; filename looks like ``"template.html"``.
# We need to eval to remove the quotes and get the string type.
+12 -7
View File
@@ -311,12 +311,13 @@ class TestDatetime(unittest.TestCase):
9, 30)), 3)
self.assertEqual(len(db(db.t.a == datetime.datetime(1971, 12,
21, 11, 30)).select()), 1)
self.assertEqual(len(db(db.t.a.year() == 1971).select()), 2)
self.assertEqual(len(db(db.t.a.month() == 12).select()), 2)
self.assertEqual(len(db(db.t.a.day() == 21).select()), 3)
self.assertEqual(len(db(db.t.a.hour() == 11).select()), 1)
self.assertEqual(len(db(db.t.a.minutes() == 30).select()), 3)
self.assertEqual(len(db(db.t.a.seconds() == 0).select()), 3)
self.assertEqual(db(db.t.a.year() == 1971).count(), 2)
self.assertEqual(db(db.t.a.month() == 12).count(), 2)
self.assertEqual(db(db.t.a.day() == 21).count(), 3)
self.assertEqual(db(db.t.a.hour() == 11).count(), 1)
self.assertEqual(db(db.t.a.minutes() == 30).count(), 3)
self.assertEqual(db(db.t.a.seconds() == 0).count(), 3)
self.assertEqual(db(db.t.a.epoch()<365*24*3600).count(),1)
db.t.drop()
@@ -329,7 +330,7 @@ class TestExpressions(unittest.TestCase):
self.assertEqual(db.t.insert(a=2), 2)
self.assertEqual(db.t.insert(a=3), 3)
self.assertEqual(db(db.t.a == 3).update(a=db.t.a + 1), 1)
self.assertEqual(len(db(db.t.a == 4).select()), 1)
self.assertEqual(db(db.t.a == 4).count(), 1)
db.t.drop()
@@ -447,18 +448,22 @@ class TestMigrations(unittest.TestCase):
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), migrate='.storage.table')
db.commit()
db.close()
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), Field('b'),
migrate='.storage.table')
db.commit()
db.close()
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), Field('b', 'text'),
migrate='.storage.table')
db.commit()
db.close()
db = DAL('sqlite://.storage.db')
db.define_table('t', Field('a'), migrate='.storage.table')
db.t.drop()
db.commit()
db.close()
def tearDown(self):
if os.path.exists('.storage.db'):
+7 -7
View File
@@ -163,17 +163,17 @@ default_application = 'defapp'
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/bad!ctl')
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/ctlr/bad!fcn')
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/init/ctlr/fcn.bad!ext')
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path \(args\)\]', filter_url, 'http://domain.com/appc/init/fcn/bad!arg')
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/appc/init/fcn/bad!arg')
except AttributeError:
pass
self.assertEqual(filter_url('http://domain.com/welcome/default/fcn_1'), "/welcome/default/fcn_1")
self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1')
try:
# 2.7+ only
self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/welcome/default/fcn-1')
except AttributeError:
pass
#self.assertRaises(HTTP, filter_url, 'http://domain.com/welcome/default/fcn-1')
#try:
# # 2.7+ only
# self.assertRaisesRegexp(HTTP, '400 BAD REQUEST \[invalid path\]', filter_url, 'http://domain.com/welcome/default/fcn-1')
#except AttributeError:
# pass
def test_routes_error(self):
'''
+12
View File
@@ -46,6 +46,18 @@ class TestWeb(unittest.TestCase):
client.get('site')
client.get('design/welcome')
class TestStaticCacheControl(unittest.TestCase):
def testWebClient(self):
s=WebClient('http://127.0.0.1:8000/welcome/')
s.get('static/js/web2py.js')
assert('expires' not in s.headers)
assert(not s.headers['cache-control'].startswith('max-age'))
text = s.text
s.get('static/_1.2.3/js/web2py.js')
assert(text == s.text)
assert('expires' in s.headers)
assert(s.headers['cache-control'].startswith('max-age'))
if __name__ == '__main__':
unittest.main()
+185 -101
View File
@@ -264,6 +264,7 @@ class Mail(object):
cc=None,
bcc=None,
reply_to=None,
sender='%(sender)s',
encoding='utf-8',
raw=False,
headers={}
@@ -606,7 +607,8 @@ class Mail(object):
# no cryptography process as usual
payload=payload_in
payload['From'] = encoded_or_raw(self.settings.sender.decode(encoding))
sender = sender % dict(sender=self.settings.sender)
payload['From'] = encoded_or_raw(sender.decode(encoding))
origTo = to[:]
if to:
payload['To'] = encoded_or_raw(', '.join(to).decode(encoding))
@@ -623,10 +625,10 @@ class Mail(object):
for k,v in headers.iteritems():
payload[k] = encoded_or_raw(v.decode(encoding))
result = {}
try:
try:
if self.settings.server == 'logging':
logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \
('-'*40,self.settings.sender,
('-'*40,sender,
', '.join(to),subject,
text or html,'-'*40))
elif self.settings.server == 'gae':
@@ -794,6 +796,10 @@ def addrow(form, a, b, c, style, _id, position=-1):
DIV(b, _class='w2p_fw'),
DIV(c, _class='w2p_fc'),
_id = _id))
elif style == "bootstrap":
form[0].insert(position, DIV(LABEL(a,_class='control-label'),
DIV(b,SPAN(c, _class='inline-help'),_class='controls'),
_class='control-group',_id = _id))
else:
form[0].insert(position, TR(TD(LABEL(a),_class='w2p_fl'),
TD(b,_class='w2p_fw'),
@@ -1044,7 +1050,7 @@ class Auth(object):
def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user',
cas_provider=None, signature=True):
cas_provider=None, signature=True, secure=False):
"""
auth=Auth(db)
@@ -1064,6 +1070,8 @@ class Auth(object):
session = current.session
auth = session.auth
self.user_groups = auth and auth.user_groups or {}
if secure:
request.requires_https()
if auth and auth.last_visit and auth.last_visit + \
datetime.timedelta(days=0, seconds=auth.expiration) > request.now:
self.user = auth.user
@@ -1191,7 +1199,10 @@ class Auth(object):
'reset_password','request_reset_password',
'change_password','profile','groups',
'impersonate','not_authorized'):
return getattr(self,args[0])()
if len(request.args) >= 2 and args[0]=='impersonate':
return getattr(self,args[0])(request.args[1])
else:
return getattr(self,args[0])()
elif args[0]=='cas' and not self.settings.cas_provider:
if args(1) == self.settings.cas_actions['login']:
return self.cas_login(version=2)
@@ -1598,7 +1609,7 @@ class Auth(object):
if settings.cas_provider: ### THIS IS NOT LAZY
settings.actions_disabled = \
['profile','register','change_password',
'request_reset_password']
'request_reset_password','retrieve_username']
from gluon.contrib.login_methods.cas_auth import CasAuth
maps = settings.cas_maps
if not maps:
@@ -1616,6 +1627,7 @@ class Auth(object):
urlbase = settings.cas_provider,
actions=actions,
maps=maps)
return self
def log_event(self, description, vars=None, origin='auth'):
"""
@@ -1700,7 +1712,8 @@ class Auth(object):
"""
login the user = db.auth_user(id)
"""
# user=Storage(self.table_user()._filter_fields(user,id=True))
user = Storage(self.table_user()._filter_fields(user,id=True))
if 'password' in user: del user.password
current.session.auth = Storage(
user = user,
last_visit = current.request.now,
@@ -1901,20 +1914,33 @@ class Auth(object):
if self.settings.remember_me_form:
## adds a new input checkbox "remember me for longer"
addrow(form,XML("&nbsp;"),
DIV(XML("&nbsp;"),
INPUT(_type='checkbox',
_class='checkbox',
_id="auth_user_remember",
_name="remember",
),
XML("&nbsp;&nbsp;"),
if self.settings.formstyle != 'bootstrap':
addrow(form,XML("&nbsp;"),
DIV(XML("&nbsp;"),
INPUT(_type='checkbox',
_class='checkbox',
_id="auth_user_remember",
_name="remember",
),
XML("&nbsp;&nbsp;"),
LABEL(
self.messages.label_remember_me,
_for="auth_user_remember",
)),"",
self.settings.formstyle,
'auth_user_remember__row')
elif self.settings.formstyle == 'bootstrap':
addrow(form,
"",
LABEL(
self.messages.label_remember_me,
_for="auth_user_remember",
)),"",
self.settings.formstyle,
'auth_user_remember__row')
INPUT(_type='checkbox',
_id="auth_user_remember",
_name="remember"),
self.messages.label_remember_me,
_class="checkbox"),
"",
self.settings.formstyle,
'auth_user_remember__row')
captcha = self.settings.login_captcha or \
(self.settings.login_captcha!=False and self.settings.captcha)
@@ -2131,6 +2157,9 @@ class Auth(object):
repr(request.vars.get(passfield, None)),
error_message=self.messages.mismatched_password))
if formstyle == 'bootstrap' :
form.custom.widget.password_two['_class'] = 'input-xlarge'
addrow(form, self.messages.verify_password + self.settings.label_separator,
form.custom.widget.password_two,
self.messages.verify_password_comment,
@@ -2420,7 +2449,7 @@ class Auth(object):
session = current.session
if next is DEFAULT:
next = self.settings.reset_password_next
next = self.next or self.settings.reset_password_next
try:
key = request.vars.key or getarg(-1)
t0 = int(key.split('-')[0])
@@ -2723,7 +2752,7 @@ class Auth(object):
self.user = session.auth.user
if requested_id is DEFAULT and not request.post_vars:
return SQLFORM.factory(Field('user_id', 'integer'))
return self.user
return SQLFORM(table_user, user.id, readonly=True)
def update_groups(self):
if not self.user:
@@ -3179,7 +3208,7 @@ class Auth(object):
def wiki(self,
slug=None,
env=None,
render=None,
render='markmin',
manage_permissions=False,
force_prefix='',
restrict_search=False,
@@ -3318,6 +3347,7 @@ class Crud(object):
message=DEFAULT,
deletable=DEFAULT,
formname=DEFAULT,
**attributes
):
"""
method: Crud.update(table, record, [next=DEFAULT
@@ -3370,7 +3400,8 @@ class Crud(object):
deletable=deletable,
upload=self.settings.download_url,
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
separator=self.settings.label_separator,
**attributes
)
self.accepted = False
self.deleted = False
@@ -3428,6 +3459,7 @@ class Crud(object):
log=DEFAULT,
message=DEFAULT,
formname=DEFAULT,
**attributes
):
"""
method: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
@@ -3454,6 +3486,7 @@ class Crud(object):
message=message,
deletable=False,
formname=formname,
**attributes
)
def read(self, table, record):
@@ -4313,34 +4346,39 @@ def prettydate(d,T=lambda x:x):
return ''
else:
return '[invalid date]'
if dt.days < 0:
suffix = ' from now'
dt = -dt
else:
suffix = ' ago'
if dt.days >= 2*365:
return T('%d years ago') % int(dt.days / 365)
return T('%d years'+suffix) % int(dt.days / 365)
elif dt.days >= 365:
return T('1 year ago')
return T('1 year'+suffix)
elif dt.days >= 60:
return T('%d months ago') % int(dt.days / 30)
return T('%d months'+suffix) % int(dt.days / 30)
elif dt.days > 21:
return T('1 month ago')
return T('1 month'+suffix)
elif dt.days >= 14:
return T('%d weeks ago') % int(dt.days / 7)
return T('%d weeks'+suffix) % int(dt.days / 7)
elif dt.days >= 7:
return T('1 week ago')
return T('1 week'+suffix)
elif dt.days > 1:
return T('%d days ago') % dt.days
return T('%d days'+suffix) % dt.days
elif dt.days == 1:
return T('1 day ago')
return T('1 day'+suffix)
elif dt.seconds >= 2*60*60:
return T('%d hours ago') % int(dt.seconds / 3600)
return T('%d hours'+suffix) % int(dt.seconds / 3600)
elif dt.seconds >= 60*60:
return T('1 hour ago')
return T('1 hour'+suffix)
elif dt.seconds >= 2*60:
return T('%d minutes ago') % int(dt.seconds / 60)
return T('%d minutes'+suffix) % int(dt.seconds / 60)
elif dt.seconds >= 60:
return T('1 minute ago')
return T('1 minute'+suffix)
elif dt.seconds > 1:
return T('%d seconds ago') % dt.seconds
return T('%d seconds'+suffix) % dt.seconds
elif dt.seconds == 1:
return T('1 second ago')
return T('1 second'+suffix)
else:
return T('now')
@@ -4539,6 +4577,7 @@ class Wiki(object):
self.env['component'] = Wiki.component
if render == 'markmin': render=self.markmin_render
elif render == 'html': render=self.html_render
self.render = render
self.auth = auth
if self.auth.user:
self.force_prefix = force_prefix % self.auth.user
@@ -4548,47 +4587,56 @@ class Wiki(object):
perms = self.manage_permissions = manage_permissions
self.restrict_search = restrict_search
db = auth.db
table_definitions = {
'wiki_page':{
'args':[
Field('slug',
requires=[IS_SLUG(),
IS_NOT_IN_DB(db,'wiki_page.slug')],
readable=False,writable=False),
Field('title',unique=True),
Field('body','text',notnull=True),
Field('tags','list:string'),
Field('can_read','list:string',
writable=perms,
readable=perms,
default=[Wiki.everybody]),
Field('can_edit', 'list:string',
writable=perms,readable=perms,
default=[Wiki.everybody]),
Field('changelog'),
Field('html','text',compute=render,
readable=False, writable=False),
auth.signature],
'vars':{'format':'%(title)s'}},
'wiki_tag':{
'args':[
Field('name'),
Field('wiki_page','reference wiki_page'),
auth.signature],
'vars':{'format':'%(name)s'}},
'wiki_media':{
'args':[
Field('wiki_page','reference wiki_page'),
Field('title',required=True),
Field('file','upload',required=True),
auth.signature],
'vars':{'format':'%(title)s'}}
}
table_definitions = [
('wiki_page',{
'args':[
Field('slug',
requires=[IS_SLUG(),
IS_NOT_IN_DB(db,'wiki_page.slug')],
readable=False,writable=False),
Field('title',unique=True),
Field('body','text',notnull=True),
Field('tags','list:string'),
Field('can_read','list:string',
writable=perms,
readable=perms,
default=[Wiki.everybody]),
Field('can_edit', 'list:string',
writable=perms,readable=perms,
default=[Wiki.everybody]),
Field('changelog'),
Field('html','text',compute=render,
readable=False, writable=False),
auth.signature],
'vars':{'format':'%(title)s'}}),
('wiki_tag',{
'args':[
Field('name'),
Field('wiki_page','reference wiki_page'),
auth.signature],
'vars':{'format':'%(name)s'}}),
('wiki_media',{
'args':[
Field('wiki_page','reference wiki_page'),
Field('title',required=True),
Field('filename','upload',required=True),
auth.signature],
'vars':{'format':'%(title)s'}})
]
# define only non-existent tables
for key, value in table_definitions.iteritems():
for key, value in table_definitions:
args = []
if not key in db.tables():
db.define_table(key, *value['args'], **value['vars'])
# look for wiki_ extra fields in auth.settings
extra_fields = auth.settings.extra_fields
if extra_fields:
if key in extra_fields:
if extra_fields[key]:
for field in extra_fields[key]:
args.append(field)
args += value['args']
db.define_table(key, *args, **value['vars'])
def update_tags_insert(page,id,db=db):
for tag in page.tags or []:
@@ -4665,17 +4713,19 @@ class Wiki(object):
)
elif zero=='_cloud':
return self.cloud()
elif zero == '_preview':
return self.preview(self.render)
def first_paragraph(self,page):
if not self.can_read(page):
mm = page.body.replace('\r','')
mm = (page.body or '').replace('\r','')
ps = [p for p in mm.split('\n\n') \
if not p.startswith('#') and p.strip()]
if ps: return ps[0]
return ''
def fix_hostname(self,body):
return body.replace('://HOSTNAME','://%s' % self.host)
return (body or '').replace('://HOSTNAME','://%s' % self.host)
def read(self,slug):
if slug in '_cloud':
@@ -4730,7 +4780,8 @@ class Wiki(object):
db.wiki_page.title.default = title_guess
db.wiki_page.slug.default = slug
if slug == 'wiki-menu':
db.wiki_page.body.default = '- Menu Item > @////index\n- - Submenu > http://web2py.com'
db.wiki_page.body.default = \
'- Menu Item > @////index\n- - Submenu > http://web2py.com'
else:
db.wiki_page.body.default = '## %s\n\npage content' % title_guess
vars = current.request.post_vars
@@ -4744,17 +4795,41 @@ class Wiki(object):
elif form.accepted:
current.session.flash = 'page created'
redirect(URL(args=slug))
return dict(content=form)
script = """
$(function() {
if (!$('#wiki_page_body').length) return;
var pagecontent = $('#wiki_page_body');
pagecontent.css('font-family', 'Monaco,Menlo,Consolas,"Courier New",monospace');
var prevbutton = $('<button class="btn nopreview">Preview</button>');
var preview = $('<div id="preview"></div>').hide();
var table = $('form');
prevbutton.insertBefore(table);
preview.insertBefore(table);
prevbutton.on('click', function(e) {
e.preventDefault();
if (prevbutton.hasClass('nopreview')) {
prevbutton.addClass('preview').removeClass('nopreview').html('Edit Source');
web2py_ajax_page('post', '%(url)s', {body : $('#wiki_page_body').val()}, 'preview');
table.fadeOut('medium', function() {preview.fadeIn()});
} else {
prevbutton.addClass('nopreview').removeClass('preview').html('Preview');
preview.fadeOut('medium', function() {table.fadeIn()});
}
})
})
""" % dict(url=URL(args=('_preview')))
return dict(content=TAG[''](form, SCRIPT(script)))
def editmedia(self,slug):
auth = self.auth
db = auth.db
page = db.wiki_page(slug=slug)
if not (page and self.can_edit(page)): return self.not_authorized(page)
self.auth.db.wiki_media.id.represent = lambda id,row:\
self.auth.db.wiki_media.id.represent = lambda id,row: \
id if not row.filename else \
SPAN('@////%i/%s.%s' % \
(id,IS_SLUG.urlify(row.title.split('.')[0]),
row.file.split('.')[-1]))
row.filename.split('.')[-1]))
self.auth.db.wiki_media.wiki_page.default = page.id
self.auth.db.wiki_media.wiki_page.writable = False
content = SQLFORM.grid(
@@ -4799,7 +4874,7 @@ class Wiki(object):
if self.manage_permissions:
page = db.wiki_page(media.wiki_page)
if not self.can_read(page): return self.not_authorized(page)
request.args = [media.file]
request.args = [media.filename]
return current.response.download(request,db)
else:
raise HTTP(404)
@@ -4884,12 +4959,13 @@ class Wiki(object):
if query is None:
query = (db.wiki_page.id==db.wiki_tag.wiki_page)&\
(db.wiki_tag.name.belongs(tags))
query = query|db.wiki_page.title.startswith(request.vars.q)
query = query|db.wiki_page.title.contains(request.vars.q)
if self.restrict_search and not self.manage():
query = query&(db.wiki_page.created_by==self.auth.user_id)
pages = db(query).select(
pages = db(query).select(count,
*fields,**dict(orderby=orderby or ~count,
groupby=db.wiki_page.id,
groupby=reduce(lambda a,b:a|b,fields),
distinct=True,
limitby=limitby))
if request.extension in ('html','load'):
if not pages:
@@ -4897,18 +4973,19 @@ class Wiki(object):
_class='w2p_wiki_form'))
def link(t):
return A(t,_href=URL(args='_search',vars=dict(q=t)))
items = [DIV(H3(A(p.title,_href=URL(args=p.slug))),
MARKMIN(self.first_paragraph(p)) \
items = [DIV(H3(A(p.wiki_page.title,_href=URL(
args=p.wiki_page.slug))),
MARKMIN(self.first_paragraph(p.wiki_page)) \
if preview else '',
DIV(_class='w2p_wiki_tags',
*[link(t.strip()) for t in \
p.tags or [] if t.strip()]),
p.wiki_page.tags or [] if t.strip()]),
_class='w2p_wiki_search_item')
for p in pages]
content.append(DIV(_class='w2p_wiki_pages',*items))
else:
cloud=False
content = [p.as_dict() for p in pages]
content = [p.wiki_page.as_dict() for p in pages]
elif cloud:
content.append(self.cloud()['content'])
if request.extension=='load':
@@ -4919,19 +4996,26 @@ class Wiki(object):
count = db.wiki_tag.wiki_page.count(distinct=True)
ids = db(db.wiki_tag).select(
db.wiki_tag.name,count,
groupby=db.wiki_tag.name,
orderby=~count,limitby=(0,20))
distinct=True,
groupby = db.wiki_tag.name,
orderby = ~count, limitby=(0,20))
if ids:
a,b = ids[0](count), ids[-1](count)
def scale(c):
return '%.2f' % (3.0*(c-b)/max(a-b,1)+1)
items = [A(item.wiki_tag.name,
_style='padding:0.2em;font-size:%sem' \
% scale(item(count)),
_href=URL(args='_search',
vars=dict(q=item.wiki_tag.name)))
for item in ids]
return dict(content=DIV(_class='w2p_cloud',*items))
def style(c):
STYLE ='padding:0 0.2em;line-height:%.2fem;font-size:%.2fem'
size = (1.5*(c-b)/max(a-b,1)+1.3)
return STYLE % (1.3,size)
items = []
for item in ids:
items.append(A(item.wiki_tag.name,
_style=style(item(count)),
_href=URL(args='_search',
vars=dict(q=item.wiki_tag.name))))
items.append(' ')
return dict(content = DIV(_class='w2p_cloud',*items))
def preview(self, render):
request = current.request
return render(request.post_vars)
if __name__ == '__main__':
import doctest
+46 -15
View File
@@ -9,6 +9,9 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
This file specifically includes utilities for security.
"""
import string
import threading
import struct
import hashlib
import hmac
import uuid
@@ -18,7 +21,11 @@ import os
import re
import logging
import socket
from contrib.pbkdf2 import pbkdf2_hex
try:
from contrib.pbkdf2 import pbkdf2_hex
HAVE_PBKDF2 = True
except ImportError:
HAVE_PBKDF2 = False
logger = logging.getLogger("web2py")
@@ -88,7 +95,7 @@ DIGEST_ALG_BY_SIZE = {
}
### compute constant ctokens
### compute constant CTOKENS
def initialize_urandom():
"""
This function and the web2py_uuid follow from the following discussion:
@@ -108,6 +115,7 @@ def initialize_urandom():
random.seed(node_id + microseconds)
try:
os.urandom(1)
have_urandom = True
try:
# try to add process-specific entropy
frandom = open('/dev/urandom','wb')
@@ -119,14 +127,33 @@ def initialize_urandom():
# works anyway
pass
except NotImplementedError:
have_urandom = False
logger.warning(
"""Cryptographically secure session management is not possible on your system because
your system does not provide a cryptographically secure entropy source.
This is not specific to web2py; consider deploying on a different operating system.""")
return ctokens
ctokens = initialize_urandom()
unpacked_ctokens = struct.unpack('=QQ',string.join(
(chr(x) for x in ctokens),''))
return unpacked_ctokens, have_urandom
UNPACKED_CTOKENS, HAVE_URANDOM = initialize_urandom()
def web2py_uuid():
def fast_urandom16(urandom=[], locker = threading.RLock()):
"""
this is 4x faster than calling os.urandom(16) and prevents
the "too many files open" issue with concurrent access to os.urandom()
"""
try:
return urandom.pop()
except IndexError:
try:
locker.acquire()
ur = os.urandom(16*1024)
urandom += [ur[i:i+16] for i in xrange(16,1024*16,16)]
return ur[0:16]
finally:
locker.release()
def web2py_uuid(ctokens=UNPACKED_CTOKENS):
"""
This function follows from the following discussion:
http://groups.google.com/group/web2py-developers/browse_thread/thread/7fd5789a7da3f09
@@ -134,15 +161,17 @@ def web2py_uuid():
It works like uuid.uuid4 except that tries to use os.urandom() if possible
and it XORs the output with the tokens uniquely associated with this machine.
"""
bytes = [random.randrange(256) for i in range(16)]
try:
ubytes = [ord(c) for c in os.urandom(16)] # use /dev/urandom if possible
bytes = [bytes[i] ^ ubytes[i] for i in range(16)]
except NotImplementedError:
pass
## xor bytes with constant ctokens
bytes = ''.join(chr(c ^ ctokens[i]) for i,c in enumerate(bytes))
return str(uuid.UUID(bytes=bytes, version=4))
rand_longs = (random.getrandbits(64),random.getrandbits(64))
if HAVE_URANDOM:
urand_longs = struct.unpack('=QQ', fast_urandom16())
byte_s = struct.pack('=QQ',
rand_longs[0]^urand_longs[0]^ctokens[0],
rand_longs[1]^urand_longs[1]^ctokens[1])
else:
byte_s = struct.pack('=QQ',
rand_longs[0]^ctokens[0],
rand_longs[1]^ctokens[1])
return str(uuid.UUID(bytes=byte_s, version=4))
REGEX_IPv4 = re.compile('(\d+)\.(\d+)\.(\d+)\.(\d+)')
@@ -161,6 +190,8 @@ def is_valid_ip_address(address):
elif address.lower() in ('unkown',''):
return False
elif address.count('.')==3: # assume IPv4
if address.startswith('::ffff:'):
address = address[7:]
if hasattr(socket,'inet_aton'): # try validate using the OS
try:
addr = socket.inet_aton(address)
@@ -169,7 +200,7 @@ def is_valid_ip_address(address):
return False
else: # try validate using Regex
match = REGEX_IPv4.match(address)
if match and all(0<=int(math.group(i))<256 for i in (1,2,3,4)):
if match and all(0<=int(match.group(i))<256 for i in (1,2,3,4)):
return True
return False
elif hasattr(socket,'inet_pton'): # assume IPv6, try using the OS
+119 -20
View File
@@ -117,6 +117,10 @@ class Validator(object):
"""
return value
def __call__(self,value):
raise NotImplementedError
return (value, None)
class IS_MATCH(Validator):
"""
@@ -462,12 +466,13 @@ class IS_IN_DB(Validator):
groupby = self.groupby
distinct = self.distinct
dd = dict(orderby=orderby, groupby=groupby,
distinct=distinct, cache=self.cache)
distinct=distinct, cache=self.cache,
cacheable=True)
records = self.dbset(table).select(*fields, **dd)
else:
orderby = self.orderby or \
reduce(lambda a,b:a|b,(f for f in fields if not f.name=='id'))
dd = dict(orderby=orderby, cache=self.cache)
dd = dict(orderby=orderby, cache=self.cache, cacheable=True)
records = self.dbset(table).select(table.ALL, **dd)
self.theset = [str(r[self.kfield]) for r in records]
if isinstance(self.label,str):
@@ -488,6 +493,8 @@ class IS_IN_DB(Validator):
table = self.dbset.db[self.ktable]
field = table[self.kfield]
if self.multiple:
if self._and:
raise NotImplementedError
if isinstance(value,list):
values=value
elif value:
@@ -497,8 +504,20 @@ class IS_IN_DB(Validator):
if isinstance(self.multiple,(tuple,list)) and \
not self.multiple[0]<=len(values)<self.multiple[1]:
return (values, translate(self.error_message))
if self.dbset(field.belongs(values)).count()==len(values):
return (values, None)
if self.theset:
if not [v for v in values if not v in self.theset]:
return (values, None)
else:
from dal import GoogleDatastoreAdapter
def count(values, s=self.dbset, f=field):
return s(f.belongs(map(int,values))).count()
if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
range_ids = range(0,len(values),30)
total = sum(count(values[i:i+30]) for i in range_ids)
if total == len(values):
return (values, None)
elif count(values) == len(values):
return (values, None)
elif self.theset:
if str(value) in self.theset:
if self._and:
@@ -2329,11 +2348,12 @@ class IS_LIST_OF(Validator):
new_value = []
if self.other:
for item in ivalue:
(v, e) = self.other(item)
if e:
return (value, e)
else:
new_value.append(v)
if item.strip():
(v, e) = self.other(item)
if e:
return (ivalue, e)
else:
new_value.append(v)
ivalue = new_value
return (ivalue, None)
@@ -2592,7 +2612,9 @@ class LazyCrypt(object):
key = self.crypt.key
else:
key = ''
if stored_password.count('$')==2:
if stored_password is None:
return False
elif stored_password.count('$')==2:
(digest_alg, salt, hash) = stored_password.split('$')
h = simple_hash(self.password, key, salt, digest_alg)
temp_pass = '%s$%s$%s' % (digest_alg, salt, h)
@@ -2707,6 +2729,43 @@ class CRYPT(object):
return ('', translate(self.error_message))
return (LazyCrypt(self,value),None)
# entropy calculator for IS_STRONG
#
lowerset = frozenset(unicode('abcdefghijklmnopqrstuvwxyz'))
upperset = frozenset(unicode('ABCDEFGHIJKLMNOPQRSTUVWXYZ'))
numberset = frozenset(unicode('0123456789'))
sym1set = frozenset(unicode('!@#$%^&*()'))
sym2set = frozenset(unicode('~`-_=+[]{}\\|;:\'",.<>?/'))
otherset = frozenset(unicode('0123456789abcdefghijklmnopqrstuvwxyz')) # anything else
def calc_entropy(string):
" calculate a simple entropy for a given string "
import math
alphabet = 0 # alphabet size
other = set()
seen = set()
lastset = None
if isinstance(string, str):
string = unicode(string, encoding='utf8')
for c in string:
# classify this character
inset = otherset
for cset in (lowerset, upperset, numberset, sym1set, sym2set):
if c in cset:
inset = cset
break
# calculate effect of character on alphabet size
if inset not in seen:
seen.add(inset)
alphabet += len(inset) # credit for a new character set
elif c not in other:
alphabet += 1 # credit for unique characters
other.add(c)
if inset is not lastset:
alphabet += 1 # credit for set transitions
lastset = cset
entropy = len(string) * math.log(alphabet) / 0.6931471805599453 # math.log(2)
return round(entropy, 2)
class IS_STRONG(object):
"""
@@ -2716,23 +2775,61 @@ class IS_STRONG(object):
requires=IS_STRONG(min=10, special=2, upper=2))
enforces complexity requirements on a field
>>> IS_STRONG(es=True)('Abcd1234')
('Abcd1234', 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|')
>>> IS_STRONG(es=True)('Abcd1234!')
('Abcd1234!', None)
>>> IS_STRONG(es=True, entropy=1)('a')
('a', None)
>>> IS_STRONG(es=True, entropy=1, min=2)('a')
('a', 'Minimum length is 2')
>>> IS_STRONG(es=True, entropy=100)('abc123')
('abc123', 'Entropy (32.35) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('and')
('and', 'Entropy (14.57) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('aaa')
('aaa', 'Entropy (14.42) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('a1d')
('a1d', 'Entropy (15.97) less than required (100)')
>>> IS_STRONG(es=True, entropy=100)('añd')
('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)')
"""
def __init__(self, min=8, max=20, upper=1, lower=1, number=1,
special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
invalid=' "', error_message=None):
self.min = min
self.max = max
self.upper = upper
self.lower = lower
self.number = number
self.special = special
def __init__(self, min=None, max=None, upper=None, lower=None, number=None,
entropy=None,
special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
invalid=' "', error_message=None, es=False):
self.entropy = entropy
if entropy is None:
# enforce default requirements
self.min = 8 if min is None else min
self.max = max # was 20, but that doesn't make sense
self.upper = 1 if upper is None else upper
self.lower = 1 if lower is None else lower
self.number = 1 if number is None else number
self.special = 1 if special is None else special
else:
# by default, an entropy spec is exclusive
self.min = min
self.max = max
self.upper = upper
self.lower = lower
self.number = number
self.special = special
self.specials = specials
self.invalid = invalid
self.error_message = error_message
self.estring = es # return error message as string (for doctest)
def __call__(self, value):
failures = []
if self.entropy is not None:
entropy = calc_entropy(value)
if entropy < self.entropy:
failures.append(translate("Entropy (%(have)s) less than required (%(need)s)") \
% dict(have=entropy, need=self.entropy))
if type(self.min) == int and self.min > 0:
if not len(value) >= self.min:
failures.append(translate("Minimum length is %s") % self.min)
@@ -2743,7 +2840,7 @@ class IS_STRONG(object):
all_special = [ch in value for ch in self.specials]
if self.special > 0:
if not all_special.count(True) >= self.special:
failures.append(translate("Must include at least %s of the following : %s") \
failures.append(translate("Must include at least %s of the following: %s") \
% (self.special, self.specials))
if self.invalid:
all_invalid = [ch in value for ch in self.invalid]
@@ -2783,6 +2880,8 @@ class IS_STRONG(object):
if len(failures) == 0:
return (value, None)
if not self.error_message:
if self.estring:
return (value, '|'.join(failures))
from html import XML
return (value, XML('<br />'.join(failures)))
else:
+32 -18
View File
@@ -804,12 +804,12 @@ def console():
default=False,
help=msg)
parser.add_option('-N',
'--no-cron',
parser.add_option('-Y',
'--run-cron',
action='store_true',
dest='nocron',
dest='runcron',
default=False,
help='do not start cron automatically')
help='start the background cron process')
parser.add_option('-J',
'--cronjob',
@@ -904,7 +904,7 @@ def console():
if options.cronjob:
global_settings.cronjob = True # tell the world
options.nocron = True # don't start cron jobs
options.run = False # don't start cron jobs
options.plain = True # cronjobs use a plain shell
options.nobanner = True
options.nogui = True
@@ -955,6 +955,19 @@ def check_existent_app(options,appname):
if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
return True
def get_code_for_scheduler(app, options):
if len(app) == 1 or app[1] == None:
code = "from gluon import current;current._scheduler.loop()"
else:
code = "from gluon import current;current._scheduler.group_names = ['%s'];"
code += "current._scheduler.loop()"
code = code % ("','".join(app[1:]))
app_ = app[0]
if not check_existent_app(options, app_):
print "Application '%s' doesn't exist, skipping" % (app_)
return None, None
return app_, code
def start_schedulers(options):
try:
from multiprocessing import Process
@@ -965,20 +978,21 @@ def start_schedulers(options):
apps = [(app.strip(), None) for app in options.scheduler.split(',')]
if options.scheduler_groups:
apps = options.scheduler_groups
code = "from gluon import current;current._scheduler.loop()"
logging.getLogger().setLevel(options.debuglevel)
if len(apps) == 1 and not options.with_scheduler:
app_, code = get_code_for_scheduler(apps[0], options)
if not app_:
return
print 'starting single-scheduler for "%s"...' % app_
run(app_,True,True,None,False,code)
return
for app in apps:
if len(app) == 1 or app[1] == None:
code = "from gluon import current;current._scheduler.loop()"
else:
code = "from gluon import current;current._scheduler.group_names = ['%s'];"
code += "current._scheduler.loop()"
code = code % ("','".join(app[1:]))
app_ = app[0]
if not check_existent_app(options, app_):
print "Application '%s' doesn't exist, skipping" % (app_)
app_, code = get_code_for_scheduler(app, options)
if not app_:
continue
print 'starting scheduler for "%s"...' % app_
args = (app_,True,True,None,False,code)
logging.getLogger().setLevel(options.debuglevel)
p = Process(target=run, args=args)
processes.append(p)
print "Currently running %s scheduler processes" % (len(processes))
@@ -1069,13 +1083,13 @@ def start(cron=True):
return
# ## if -N or not cron disable cron in this *process*
# ## if -H cron is enabled in this *process*
# ## if --softcron use softcron
# ## use hardcron in all other cases
if cron and not options.nocron and options.softcron:
if cron and options.runcron and options.softcron:
print 'Using softcron (but this is not very efficient)'
global_settings.web2py_crontype = 'soft'
elif cron and not options.nocron:
elif cron and options.runcron:
logger.debug('Starting hardcron...')
global_settings.web2py_crontype = 'hard'
newcron.hardcron(options.folder).start()
+2 -2
View File
@@ -94,8 +94,6 @@ class Web2pyService(Service):
os.chdir(dir)
from gluon.settings import global_settings
global_settings.gluon_parent = dir
from gluon.custom_import import custom_import_install
custom_import_install(dir)
return True
except:
self.log("Can't change to web2py working path; server is stopped")
@@ -154,6 +152,8 @@ class Web2pyService(Service):
def web2py_windows_service_handler(argv=None, opt_file='options'):
path = os.path.dirname(__file__)
web2py_path = up(path)
if web2py_path.endswith('.zip'): # in case bianry distro 'library.zip'
web2py_path = os.path.dirname(web2py_path)
os.chdir(web2py_path)
classstring = os.path.normpath(
os.path.join(web2py_path,'gluon.winservice.Web2pyService'))
+2 -2
View File
@@ -76,8 +76,8 @@ propagate=0
# welcome app handler
[logger_welcome]
level=WARNING
qualname=web2py.app.welcome,rotatingFileHandler
handlers=consoleHandler
qualname=web2py.app.welcome
handlers=consoleHandler,rotatingFileHandler
propagate=0
# loggers for legacy getLogger calls: Rocket and markdown
+6 -4
View File
@@ -35,11 +35,13 @@ BASE = '' # optonal prefix for incoming URLs
routes_in = (
# do not reroute admin unless you want to disable it
(BASE+'/admin/?$anything','/admin/$anything'),
(BASE+'/admin','/admin/default/index'),
(BASE+'/admin/$anything','/admin/$anything'),
# do not reroute appadmin unless you want to disable it
(BASE+'/$app/appadmin/?$anything','/$app/appadmin/$anything'),
(BASE+'/$app/appadmin','/$app/appadmin/index'),
(BASE+'/$app/appadmin/$anything','/$app/appadmin/$anything'),
# do not reroute static files
(BASE+'/$app/static/?$anything','/$app/static/$anything'),
(BASE+'/$app/static/$anything','/$app/static/$anything'),
# reroute favicon and robots, use exable for lack of better choice
('/favicon.ico', '/examples/static/favicon.ico'),
('/robots.txt', '/examples/static/robots.txt'),
@@ -55,7 +57,7 @@ routes_in = (
routes_out = (
# do not reroute admin unless you want to disable it
('/admin/$anything', BASE+'/admin/?$anything'),
('/admin/$anything', BASE+'/admin/$anything'),
# do not reroute appadmin unless you want to disable it
('/$app/appadmin/$anything',BASE+'/$app/appadmin/$anything'),
# do not reroute static files
-25
View File
@@ -1,25 +0,0 @@
import os
import sys
paths = [sys.argv[1]]
paths1 = []
paths2 = []
while paths:
path = paths.pop()
for filename in os.listdir(path):
fullname = os.path.join(path,filename)
if os.path.isdir(fullname):
paths.append(fullname)
else:
extension = filename.split('.')[-1]
if extension.lower() in ('png','gif','jpg','jpeg','js','css'):
paths1.append((filename,fullname))
if extension.lower() in ('css','js','py','html'):
paths2.append(fullname)
for filename,fullname in paths1:
for otherfullname in paths2:
if open(otherfullname).read().find(filename)>=0:
break
else:
print fullname
# os.system('hg rm '+fullname)
# os.system('rm '+fullname)
+1 -1
View File
@@ -23,7 +23,7 @@ applications/welcome/controllers/default.py
# files and folders to exclude from gluon folder (comment with # if needed)
IGNORED = """
gluon/contrib/comet_messaging.py
gluon/contrib/websocket_messaging.py
gluon/contrib/feedparser.py
gluon/contrib/generics.py
gluon/contrib/gql.py
+16 -7
View File
@@ -94,11 +94,11 @@ class SessionSetDb(SessionSet):
"""Return list of SessionDb instances for existing sessions."""
sessions = []
tablename = 'web2py_session'
if request.application:
tablename = 'web2py_session_' + request.application
if tablename in db:
for row in db(db[tablename].id > 0).select():
sessions.append(SessionDb(row))
from gluon import current
(record_id_name, table, record_id, unique_key) = \
current.response._dbtable_and_field
for row in table._db(table.id > 0).select():
sessions.append(SessionDb(row))
return sessions
@@ -121,8 +121,11 @@ class SessionDb(object):
self.row = row
def delete(self):
from gluon import current
(record_id_name, table, record_id, unique_key) = \
current.response._dbtable_and_field
self.row.delete_record()
db.commit()
table._db.commit()
def get(self):
session = Storage()
@@ -130,7 +133,13 @@ class SessionDb(object):
return session
def last_visit_default(self):
return self.row.modified_datetime
if isinstance(self.row.modified_datetime, datetime.datetime):
return self.row.modified_datetime
else:
try:
return datetime.datetime.strptime(self.row.modified_datetime, '%Y-%m-%d %H:%M:%S.%f')
except:
print 'failed to retrieve last modified time (value: %s)' % self.row.modified_datetime
def __str__(self):
return self.row.unique_key

Some files were not shown because too many files have changed in this diff Show More