Compare commits

..

268 Commits

Author SHA1 Message Date
mdipierro cda35fd48a R-2.14.6 2016-05-09 19:19:07 -05:00
mdipierro 85c37af1f4 removed unwanted file 2016-05-09 19:11:08 -05:00
mdipierro 87935a45ba Merge branch 'master' of github.com:web2py/web2py 2016-05-09 00:24:29 -05:00
mdipierro 0692272991 going back to dal 16.03 to prepare for 2.14.6 2016-05-09 00:24:14 -05:00
mdipierro c9f11c068c Merge pull request #1322 from ShySec/oneall_optimization
simplified oneall dname handling
2016-05-09 00:05:06 -05:00
mdipierro 54b0feeffb Merge pull request #1321 from ShySec/master
fixed timing attack in gluon.utils.compare
2016-05-09 00:04:39 -05:00
mdipierro 8666f993d1 Merge pull request #1320 from niphlod/enhancement/redis_scheduler
sync with main scheduler
2016-05-09 00:03:42 -05:00
kelson 822e68ac16 simplified oneall dname handling 2016-05-06 16:10:19 -04:00
kelson 292af5adc6 fixed timing attack in gluon.utils.compare 2016-05-06 14:14:32 -04:00
niphlod c6d4fb8f38 sync with main scheduler 2016-05-05 21:36:51 +02:00
mdipierro 82d79e74c6 Merge pull request #1318 from leonelcamara/admin_lockout
Check if host is denied before verifying password
2016-05-04 13:30:14 -05:00
Leonel Câmara 944d8bd8f3 Check if host is denied before verifying password 2016-05-04 19:02:53 +01:00
mdipierro 33c1144e2e fixing merge issue 2016-05-04 09:29:30 -05:00
mdipierro ca21bb0b9a added contributor Narendra Bhati(security) 2016-05-04 09:28:25 -05:00
mdipierro a2b98cd6df Merge pull request #1317 from leonelcamara/test_week9
more tests for validators
2016-05-04 09:25:30 -05:00
mdipierro 51c3b633fe remove XSS attack in installing plugin, thanks Nerendra Bhati 2016-05-04 09:21:20 -05:00
mdipierro 1e74c332d0 Merge branch 'master' of github.com:web2py/web2py 2016-05-04 09:12:52 -05:00
mdipierro 4bd002aee9 fixed CSRF in admin enabled/disable. thanks Nerendra Bhati 2016-05-04 09:11:55 -05:00
Leonel Câmara 1b42fe6547 Fixes #1316 2016-05-03 19:17:58 +01:00
Leonel Câmara 810520b3f0 more tests for validators 2016-05-03 02:38:50 +01:00
mdipierro af18582198 Merge pull request #1315 from niphlod/enhancement/scheduler
can now process tasks with huge_results
2016-05-02 10:16:38 -05:00
mdipierro 2d6ca49675 Merge pull request #1314 from chenl/issue_1310
issue#1310
2016-05-02 10:16:03 -05:00
mdipierro db36e4380d Merge pull request #1308 from leonelcamara/test_week8
partial tests for appadmin, testing a bunch of other stuff in the pro…
2016-05-02 10:13:54 -05:00
niphlod 2031a43058 can now process tasks with huge_results
Added tests, too. Cleanups leftovers better, so fixes #1304
2016-05-01 22:01:49 +02:00
Chen Rotem Levy 07d764f3c6 issue#1310
undo PR#1194
2016-04-30 13:41:11 +03:00
Leonel Câmara b8b63302f4 add test_appadmin to tests/__init__.py 2016-04-26 17:17:19 +01:00
Leonel Câmara fa1af36adf partial tests for appadmin, testing a bunch of other stuff in the process 2016-04-26 04:31:22 +01:00
mdipierro 2fc1378399 Merge pull request #1307 from zvolsky/master
script for language files update has new param to update existing tra…
2016-04-25 20:32:24 -05:00
zvolsky b6ee82bbde script for language files update has new param to update existing translations 2016-04-25 11:06:02 +02:00
mdipierro 8ed6736e82 Merge pull request #1306 from BuhtigithuB/improve/html-and-it-tests
improve html.py, new url_encode=True/False test, review/close #1279
2016-04-24 18:44:38 -05:00
mdipierro 99b083ad43 Merge pull request #1305 from leonelcamara/test_week7
100% coverage for recfile
2016-04-24 18:43:31 -05:00
Hardirc c6b844a3e4 minor improve html.py, new url_encode=True/False test, review and close 1279 2016-04-24 09:47:57 -04:00
Leonel Câmara c7bb69a0b0 consistent naming 2016-04-21 20:12:00 +01:00
Leonel Câmara 8b402b491c 100% coverage for recfile 2016-04-21 20:01:23 +01:00
mdipierro 735d79c211 latest pydal fixed connection issue 2016-04-19 22:45:49 -05:00
mdipierro 4177b4fe58 included fixes from dal 2016-04-19 11:42:24 -05:00
mdipierro 82aec9ba39 new fixes from Giovanni 2016-04-19 11:30:40 -05:00
Giovanni Barillari dc28664270 Tracking pydal 85c530c 2016-04-19 17:41:29 +02:00
Giovanni Barillari 78bb8e9b26 Updated to latest pydal, fixed IS_IN_DB validator for new pydal 2016-04-19 17:24:09 +02:00
mdipierro 0a633236e5 pydal fixes, thanks Giovanni 2016-04-19 08:17:55 -05:00
mdipierro 42d1544478 new experimental dal 2016-04-18 21:28:27 -05:00
mdipierro 85819a5f83 Merge pull request #1299 from BuhtigithuB/improve/auth-tests
New Auth tests
2016-04-17 21:27:35 -05:00
mdipierro 9ca1c28db3 Merge pull request #1298 from BuhtigithuB/new/prettydate-test-suite
New test suite for prettydate() + fix wrong number of days for month
2016-04-17 21:27:18 -05:00
Hardirc 37fa90fbd2 .has_..., .add_..., .del_permission tests cases 2016-04-17 13:28:41 -04:00
Hardirc 2f0de8d8a0 New Auth tests & del_membership('role') api harmonization 2016-04-17 11:35:17 -04:00
Hardirc 70a0209e31 Reorder tests cases and make inventory + news tests 2016-04-16 21:33:24 -04:00
Hardirc 92b3c8f777 New Auth tests 2016-04-16 19:35:06 -04:00
Hardirc d622a8aa66 New test suite for prettydate() + fix wrong number of days for month 2016-04-16 14:54:34 -04:00
mdipierro 2a2771c4cc Merge pull request #1297 from BuhtigithuB/update/markdown2-contrib
Update markdown2.py 2.2.4 -> 2.3.1 (latest release)
2016-04-15 22:10:22 -05:00
mdipierro 4fbd612d1b Merge pull request #1296 from BuhtigithuB/enhancement/pep8-tools-py
Enhancement tools.py PEP8
2016-04-15 22:10:12 -05:00
mdipierro 190c77e0e7 Merge pull request #1295 from BuhtigithuB/refactor/auth-tools-tests
Refactor Auth tests, new test, old implementation commented for now
2016-04-15 22:09:44 -05:00
Richard Vézina 4fa8b3ed67 Update markdown2.py 2.2.4 -> 2.3.1 (latest release) 2016-04-15 12:33:04 -04:00
Richard Vézina f109be363d Enhancement tools.py PEP8 2016-04-14 11:17:27 -04:00
Hardirc b7cc1b2db5 Refactor Auth tests, new tests, old implementation commented for now 2016-04-14 03:01:15 -04:00
mdipierro 81d0291ce2 R-2.14.5 2016-04-13 22:22:39 -05:00
mdipierro 894ff3c140 Merge pull request #1294 from leonelcamara/fix_1274
fixes #1274
2016-04-13 22:16:46 -05:00
mdipierro 216ce5507c Merge pull request #1293 from leonelcamara/fix_1266
fixes #1266 and adds tests to make sure it doesn't happen again
2016-04-13 22:15:53 -05:00
mdipierro 068aecff93 Merge pull request #1292 from BuhtigithuB/add-more-html-test
Some more coverage for XML() and DIV()
2016-04-13 22:15:22 -05:00
Leonel Câmara 59cbe99347 pep8 and force travis to rebuild 2016-04-14 04:13:18 +01:00
Leonel Câmara bdbc053285 fixes #1274 2016-04-14 03:57:20 +01:00
Leonel Câmara d746d43be5 pep8 and make travis run again 2016-04-14 01:21:23 +01:00
Leonel Câmara 9a3e73031b fixes #1266 and adds tests to make sure it doesn't happen again 2016-04-14 01:02:25 +01:00
Richard Vézina 0468c16bc2 Some more coverage for XML() and DIV() 2016-04-13 11:44:16 -04:00
mdipierro 8c5858b6b7 fixed merge issue 2016-04-13 08:41:09 -05:00
mdipierro 6400e28a85 fixed stupid 2016-04-13 08:40:19 -05:00
mdipierro 3e46d50aa1 Merge pull request #1290 from carpaIdea/patch-3
box-sizing best practice
2016-04-13 08:33:29 -05:00
mdipierro cb94fde80b Merge pull request #1289 from carpaIdea/patch-2
<main> semantic element IE fix
2016-04-13 08:30:20 -05:00
mdipierro 98593eefce Merge pull request #1291 from leonelcamara/test_week6
Test emails with alternative text and html
2016-04-13 08:27:33 -05:00
Leonel Câmara 4bbfe70927 install a more modern pypy version 2016-04-13 14:17:30 +01:00
Leonel Câmara 02a0d1c9b0 minor reorder 2016-04-13 14:01:36 +01:00
Leonel Câmara 2bf0ad9268 test emails with alternative text and html 2016-04-13 14:00:33 +01:00
carpaIdea 2e63b7637a box-sizing best practice
This code was updated to match new box-sizing best practices. Also prefixes are pretty much dead. (quote from http://www.paulirish.com/2012/box-sizing-border-box-ftw/).

More info on https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/
2016-04-13 00:53:42 +02:00
mdipierro 67485d16a5 R-2.14.4 2016-04-12 16:21:05 -05:00
carpaIdea a9c5cf3072 <main> semantic element IE fix
Some versions of Internet Explorer consider the <main> semantic element as "unknown". So its initial value is "inline".If we want the behavior of the <main> element to be the same as in other browsers, we must set it explicitly as a "block."
2016-04-12 23:07:27 +02:00
mdipierro eba8ad4b55 fixed CHANGELOG 2016-04-12 15:45:54 -05:00
mdipierro 4ed6fb7ed9 Merge pull request #1288 from carpaIdea/patch-1
Missing <!DOCTYPE html>
2016-04-12 15:44:19 -05:00
carpaIdea 294c67194f Missing <!DOCTYPE html>
Of course if there is not any intentional reason to turn on "quirks mode" in browsers.

https://developer.mozilla.org/en-US/docs/Web/CSS/Common_CSS_Questions#Why_doesn't_my_CSS_which_is_valid_render_correctly
https://developer.mozilla.org/it/docs/Quirks_Mode_and_Standards_Mode
2016-04-12 22:24:30 +02:00
mdipierro 70f793b422 Merge pull request #1287 from leonelcamara/test_week6
Test week6
2016-04-12 11:14:14 -05:00
Leonel Câmara 9552d9d6d0 fixed sort=True test 2016-04-12 16:28:33 +01:00
Leonel Câmara 9ead66b6db test IS_IN_DB label is a Field and self.sort = True 2016-04-12 16:00:33 +01:00
Leonel Câmara 00c65ad160 Complete coverage for Mail.Attachment 2016-04-12 15:28:48 +01:00
Leonel Câmara b5c8b3ad25 closes #1286 2016-04-12 15:10:14 +01:00
mdipierro 5ca65d55d2 Merge pull request #1284 from BuhtigithuB/more-markmin-pep8
More markmin pep8
2016-04-11 13:38:54 -05:00
mdipierro 3f200fdc22 Merge pull request #1283 from leonelcamara/grid_issues
closes #1188
2016-04-11 13:38:37 -05:00
Hardirc 409c973dc4 Enhance markmin2pdf.py PEP8 2016-04-10 09:36:06 -04:00
Hardirc 59a194842d Enhance markmin2latex.py PEP8 2016-04-10 09:34:29 -04:00
Leonel Câmara ee8b11db2c closes #1188 2016-04-10 12:08:25 +01:00
mdipierro 8ef04ac425 Merge pull request #1282 from BuhtigithuB/Feature/heading-anchor
Feature/heading anchor
2016-04-09 18:39:39 -05:00
mdipierro 83cf098c07 fixed stupid.css and impersonate 2016-04-09 10:30:31 -05:00
mdipierro 70b41fa15e Merge pull request #1280 from BuhtigithuB/pep8-gluon-html-py
Enhance gluon/html.py PEP8
2016-04-08 23:37:54 -05:00
mdipierro 56af81f247 Merge pull request #1278 from BuhtigithuB/pep8-welcome
Pep8 welcome
2016-04-08 23:37:32 -05:00
mdipierro 7fd30d8360 Merge pull request #1276 from BuhtigithuB/Improve/web2py-com-display
Improve/web2py com display
2016-04-08 23:35:43 -05:00
mdipierro e1aefa2307 Merge pull request #1275 from BuhtigithuB/Improve/gluon-tools-py
PEP8 Recaptcha/2 docstring
2016-04-08 23:35:10 -05:00
mdipierro 7a2316ec9a Merge pull request #1260 from BuhtigithuB/add-test-sqlform
SQLFORM() basic check
2016-04-08 23:34:51 -05:00
Hardirc e48e47beb2 Enhance markmin2html.py contrib PEP8 2016-04-08 22:12:43 -04:00
Hardirc 864c308246 Enhance gluon/html.py PEP8 2016-04-08 21:11:25 -04:00
Hardirc 994f3e7ae4 Enhance welcome menu.py PEP8 2016-04-08 20:17:35 -04:00
Hardirc 1d2f74440e Enhance welcome routes.example.py PEP8 2016-04-08 20:13:58 -04:00
Hardirc 704ceec16f Enhance welcome controllers/default.py PEP8 2016-04-08 20:13:30 -04:00
Hardirc 038e25c259 Enhance welcome db.py PEP8 2016-04-08 20:05:21 -04:00
Richard Vézina c3aa02b3ce Improve welcome (web2py.com) site aesthetic 2016-04-08 15:16:58 -04:00
Richard Vézina 5cc4487d8c Improve PEP8 download.html 2016-04-08 14:17:56 -04:00
Richard Vézina d50e6aab6b Improve default.py PEP8 2016-04-08 11:52:09 -04:00
Richard Vézina 1d21f45e3e PEP8 Recaptcha/2 docstring 2016-04-07 10:19:57 -04:00
mdipierro 99a323c7ad Merge pull request #1272 from BuhtigithuB/new-logout-bare
New logout_bare() for shell logout and refactor test using it
2016-04-07 09:17:19 -05:00
Hardirc e0d86462c8 New logout_bare() for shell logout and refactor test using it 2016-04-06 22:46:24 -04:00
mdipierro ff0d10ac4f Merge pull request #1271 from leonelcamara/delete_unused_reserved
delete unused reserved_sql_keywords.py because the DAL uses this one …
2016-04-06 20:58:05 -05:00
mdipierro eee7be75c8 Merge pull request #1265 from BuhtigithuB/improve-tools-tests-some-more
Improve tools tests some more
2016-04-06 20:57:30 -05:00
mdipierro 3c69716672 Merge pull request #1270 from leonelcamara/test_week5b
Test Mail.Attachment sending test_tools.py itself
2016-04-06 20:57:06 -05:00
Leonel Câmara ad4b0eee54 delete unused reserved_sql_keywords.py because the DAL uses this one pydal/contrib/reserved_sql_keywords.py 2016-04-06 23:06:57 +01:00
Richard Vézina 0128ce3a93 Make test_login_bare() works, new test_impersonate() 2016-04-06 16:58:58 -04:00
Leonel Câmara 0629df71ef Test Mail.Attachment sending test_tools.py itself 2016-04-06 20:04:21 +01:00
mdipierro 4d1a4c48e6 Merge pull request #1268 from mbelletti/fix/cas_login
Fix #1267 cas_login
2016-04-06 13:49:28 -05:00
Massimiliano Belletti 2ffdb716cd Fix #1267 cas_login 2016-04-06 17:06:23 +02:00
mdipierro 40f04de9d2 fixed scripts/setup-web2py-nginx-uwsgi-ubuntu.sh 2016-04-06 09:20:13 -05:00
mdipierro 52615fbca7 Merge pull request #1263 from niphlod/tests/scheduler
initial tests for scheduler.py, plus a minor fix for JobGraph
2016-04-05 17:00:00 -05:00
niphlod 6abb78c559 initial tests for scheduler.py 2016-04-04 22:58:42 +02:00
Hardirc db701ffea8 correct unproperly generated SQLFORM methods and basic tests 2016-04-03 16:24:13 -04:00
mdipierro 0804c28331 Merge pull request #1259 from BuhtigithuB/new-test-sqlform
let start testing sqlhtml.py
2016-04-02 15:02:02 -05:00
mdipierro 24bc51447c Merge pull request #1257 from BuhtigithuB/improve-tools-tests
Improve tools tests
2016-04-02 15:00:41 -05:00
mdipierro ccbbdc2493 Merge branch 'leonelcamara-prettier_examples' 2016-04-02 14:59:23 -05:00
mdipierro d94e8415c7 reverted stupid 2016-04-02 14:57:36 -05:00
mdipierro 0a62e86156 Merge pull request #1254 from leonelcamara/test_week4b
Seriously increase template.py coverage
2016-04-02 14:53:39 -05:00
mdipierro 5744c06f59 Merge pull request #1253 from leonelcamara/test_week4
More test coverage for validators.py
2016-04-02 14:52:42 -05:00
mdipierro 947f92774b Merge pull request #1252 from BuhtigithuB/improve-test-chache
Improve test chache
2016-04-02 14:52:26 -05:00
Hardirc 4001407add let start testing sqlhtml.py 2016-04-02 15:49:18 -04:00
mdipierro 46ffaa6aea improved search form 2016-04-02 14:48:03 -05:00
mdipierro ba2be80080 updated stupic.css and suport page 2016-04-02 14:35:28 -05:00
Richard Vézina 3a9221a2b9 Reach fully some partially hit lines 2016-03-31 17:00:50 -04:00
Richard Vézina e0eb425223 Little improvement of tools.py 2016-03-31 16:25:55 -04:00
Richard Vézina f7ad31f066 Refactor TestAuth() with setUp() and add test case 2016-03-31 16:25:02 -04:00
Leonel Câmara d0f6ef4783 made stuff prettier
added myself to the contributor list :P
2016-03-31 00:29:28 +01:00
Richard Vézina 104d616cb9 Reorder test case and make inventory of what missing 2016-03-30 16:32:37 -04:00
Richard Vézina a8703270da PEP8 enhancement 2016-03-30 16:20:02 -04:00
Leonel Câmara ad57c3c613 test block extend, super, delimiters, etc. 2016-03-30 02:33:29 +01:00
Leonel Câmara 5c292640ba Complete coverage for IS_IN_SET
Removed unreachable code in IS_IN_SET
        if failures and self.theset:
            if self.multiple and (value is None or value == '')
It's impossible to have *failures* and have value be None or '' at the same time
2016-03-28 14:47:58 +01:00
Leonel Câmara 5cbf381a2c More test coverage for validators.py
Fixed a bug in IS_EMAIL throwing exceptions when asked to validate anything other than a string which was problematic for ANY_OF
Fixed a bug in ANY_OF.formatter where it was trying to format with a validator that didn't validate
2016-03-27 14:23:24 +01:00
mdipierro a0bbd7885a Merge branch 'master' of github.com:web2py/web2py 2016-03-26 17:56:23 -05:00
mdipierro c2ce90a1fe R-2.14.3 2016-03-26 17:55:52 -05:00
mdipierro 197b018534 fixed stupid.css again 2016-03-26 17:54:11 -05:00
Hardirc 9ac1e7188f tests inventory and reordering + PEP8 enhancement 2016-03-26 13:19:29 -04:00
Hardirc 58a8ba067c cache.py PEP8 enhancement 2016-03-26 13:11:27 -04:00
mdipierro 060aeff867 Merge pull request #1251 from BuhtigithuB/add-even-more-test-over-html-py
More coverage and some PEP8 enhencement to html.py
2016-03-26 00:00:43 -05:00
mdipierro d4572c5f38 Merge branch 'master' of github.com:web2py/web2py 2016-03-25 23:57:24 -05:00
mdipierro 46dc83a1bb synced to latest stupid.css 2016-03-25 23:57:13 -05:00
Hardirc f414356b67 More coverage and some PEP8 enhencement to html.py 2016-03-26 00:44:28 -04:00
mdipierro 460a017bab Merge pull request #1250 from Pierre-Thibault/strange_lines
Nonsense sys.exc_info parameter of traceback.format_exc calls.
2016-03-25 18:07:25 -05:00
mdipierro 0b966d7c0a Merge pull request #1249 from leonelcamara/patch-1
imageutils.py missing comma bug
2016-03-25 18:06:56 -05:00
Pierre Thibault d2d16d4081 I was forgetting line 200. 2016-03-25 15:54:01 -04:00
Pierre Thibault ebcf6e5671 Nonsense sys.exc_info parameter of traceback.format_exc calls. 2016-03-25 15:48:58 -04:00
Leonel Câmara 778cc3902b Update imageutils.py
missing comma
2016-03-25 12:45:33 +00:00
mdipierro 32650f0cbf R-2.14.2 2016-03-24 17:44:41 -05:00
mdipierro 8f8ef4cca5 fixed sessions for long keys 2016-03-24 16:58:04 -05:00
mdipierro c9e92fc686 restored pickles in sessions 2016-03-24 16:50:02 -05:00
mdipierro 0820926b50 more secure sessions in cookies using json 2016-03-24 16:46:51 -05:00
mdipierro 1856c9dc7a fixed changelog 2016-03-24 12:33:43 -05:00
mdipierro 973bb4a16f R-2.14.1 2016-03-24 12:20:58 -05:00
mdipierro 2fa54f069c Merge pull request #1246 from BuhtigithuB/improve-test-validators
Improve PEP8 just a bit + more coverage
2016-03-24 12:10:17 -05:00
mdipierro b11260d2e2 Merge pull request #1244 from mbelletti/fix/admin_enable_disable_apps
Fix missing w2p_ajax_disable_with_message #1234
2016-03-24 12:10:01 -05:00
mdipierro a6a9b004ea Merge pull request #1235 from kjkuan/avoid-query-per-row-for-virtual-fields
Avoid issuing a separate sql query for each row in sqlform grid search results, for virtual fields
2016-03-24 12:09:36 -05:00
mdipierro 6ed204301d ok dropped backward compatibility in redis_*.py 2016-03-24 12:08:43 -05:00
Richard Vézina 52392a10ae Improve PEP8 just a bit + more coverage 2016-03-24 10:44:37 -04:00
Jack Kuan 6d68a40ddf Fix to work in cases, in which the query for the grid is not a join. 2016-03-24 10:25:37 -04:00
Jack Kuan 51bf802978 When deriving the value for a virtual field in a sqlform grid,
avoid issuing a separate sql query for each row found in the search
results.
2016-03-24 10:25:37 -04:00
Massimiliano Belletti 34267b7673 Fix missing w2p_ajax_disable_with_message #1234 2016-03-24 11:17:59 +01:00
mdipierro 26eb5e6f38 Merge pull request #1242 from BuhtigithuB/fix-assert-raise-test-html
fix some assertRaises leave commented + more coverage
2016-03-24 03:03:24 -05:00
mdipierro 13964c2c9b Merge pull request #1241 from gi0baro/master
Updated pyDAL to 16.03
2016-03-24 03:03:03 -05:00
mdipierro 6a876fffc2 Merge pull request #1240 from BuhtigithuB/add-utils-test
test_utils inventory and new tests
2016-03-24 03:02:50 -05:00
mdipierro cd7850cc36 Merge pull request #1239 from BuhtigithuB/rework-fileutils-tests
rework test_fileutils.py mostly
2016-03-24 03:02:34 -05:00
Hardirc e4705dd48a fix some assertRaises leave commented + more coverage 2016-03-24 03:09:47 -04:00
Richard Vézina 2ef079289b test_utils inventory and new tests 2016-03-24 01:59:39 -04:00
mdipierro 76e95a9f1c edited CHANGELOG 2016-03-23 19:32:23 -05:00
mdipierro 4c5664f701 backward compatible syntax for redis 2016-03-23 19:14:37 -05:00
mdipierro cafba3fbe2 requires 2.14.1 2016-03-23 18:52:40 -05:00
Giovanni Barillari 8cb6678505 Updated pyDAL to 16.03 2016-03-24 00:38:48 +01:00
Richard Vézina 9fabfaac96 rework test_fileutils.py mostly 2016-03-23 14:32:14 -04:00
mdipierro 797ade202f Merge pull request #1238 from BuhtigithuB/add-more-test-over-html-py
BEAUTIFY, MENU, MARKMIN and ASSIGNJS basic tests and attempt ASSIGNJS…
2016-03-23 11:34:58 -05:00
Hardirc dda81d1b95 SELECT('Option 1', 'Option 2') what else... 2016-03-23 00:25:56 -04:00
Hardirc 13ed29ffc3 OPTGROUP with OPTION 2016-03-23 00:18:17 -04:00
Hardirc 785276d294 some more corner cases for SELECT 2016-03-23 00:16:27 -04:00
Hardirc 52f634cb58 some more corner cases for TEXTAREA and commented DIV SyntaxError 2016-03-22 23:50:23 -04:00
Hardirc f921b24a95 SELECT corner cases with option, multiple and selected 2016-03-22 23:25:37 -04:00
Hardirc 753c54dbfc HTML corner case for doctype=CustomDocType 2016-03-22 23:11:45 -04:00
Hardirc 89cf314358 XHTML corner tests for doctype=None/strict/transitional/frameset/xmlns 2016-03-22 22:47:59 -04:00
Hardirc 9299ecb64d HTML corner tests for doctype=stric/transitional/frameset/html5/None 2016-03-22 22:40:06 -04:00
Hardirc 94afe61477 BEAUTIFY, MENU, MARKMIN and ASSIGNJS basic tests and attempt ASSIGNJS documentation 2016-03-22 22:27:21 -04:00
mdipierro a22f56ca0c removed unwanted print statements 2016-03-22 17:55:29 -05:00
mdipierro 656c490a21 Merge pull request #1232 from BuhtigithuB/improve-grib-button-display
a space between grid button icon and text is much more beautiful
2016-03-22 17:15:25 -05:00
mdipierro 8859ef04d3 Merge pull request #1231 from leonelcamara/test_week3b
tests for basic Mail functionality
2016-03-22 17:15:06 -05:00
mdipierro 5cf835d856 fixed session_cookie_key leak 2016-03-21 17:25:16 -05:00
mdipierro 6a569bf56e restored some of the deleted files 2016-03-21 10:37:36 -05:00
mdipierro bd6115ad62 fixed Host header vulnerability #1196 2016-03-21 01:15:46 -05:00
Hardirc 3a265e3111 a space between grid button icon and text is much more beautiful 2016-03-20 14:05:40 -04:00
Leonel Câmara 78fc14df81 minor - pep8 and naming consistency 2016-03-20 12:46:26 +00:00
Leonel Câmara 4311820494 tests for basic Mail functionality 2016-03-20 12:33:38 +00:00
mdipierro d1094e7b0c 2.14.1.beta 2016-03-19 17:16:01 -05:00
mdipierro dbbbd44642 fixed space in bootstrap forms 2016-03-19 17:10:44 -05:00
mdipierro fa9d1ccb8b fixed color or calendar popup 2016-03-19 17:05:43 -05:00
mdipierro e6fad4f007 fixed default validators 2016-03-19 17:00:35 -05:00
mdipierro 23292754e3 fixed gae logging issue 2016-03-19 15:10:19 -05:00
mdipierro dcd7f8b46c no more copy of the logfile, thanks Luca 2016-03-19 14:29:01 -05:00
mdipierro 7fd67c4e2e fixed default validators ofr string and boolean 2016-03-19 14:15:08 -05:00
mdipierro ec3ae8211f possibly fixed #1162, validation of required notnull fields, hope this does not break something else 2016-03-19 13:39:27 -05:00
mdipierro e8c0e0df92 #1192 again, going it the way Anthony suggests 2016-03-19 13:24:06 -05:00
mdipierro 7f9262f8f8 partially addressed issue #1192, comments there 2016-03-19 13:10:23 -05:00
mdipierro 11da1ed19a fixed issue #1206, translate T.M 2016-03-19 13:02:45 -05:00
mdipierro 9735477c35 requires version 2016-03-19 12:05:19 -05:00
mdipierro f6f946f867 Merge pull request #1230 from leonelcamara/test_week3
test_IS_IMAGE and more
2016-03-19 11:29:58 -05:00
Leonel Câmara 8683b0680d add test for something that is NOT an image 2016-03-19 14:55:16 +00:00
Leonel Câmara fbb5776432 test_IS_IMAGE
Removed unused and undocumented IS_IN_SUBSET
Fixed _options IS_EMPTY_OR not passing arguments to the other options method
Some tests for formatters
2016-03-19 12:52:48 +00:00
mdipierro 92b4bc4f94 fixed #778 PRE(repr()) in admin, thanks Simone 2016-03-19 04:08:33 -05:00
mdipierro d886bf759e removed lots of junk from scripts folder 2016-03-19 04:02:22 -05:00
mdipierro d5d25e8110 jquery 1.12.2 2016-03-18 20:49:19 -05:00
mdipierro 86c70df1e7 updated style and spacing 2016-03-18 20:47:59 -05:00
mdipierro b6d923753a Merge pull request #1228 from cvaroqui/master
LDAP auth login method enhancements
2016-03-18 20:33:56 -05:00
mdipierro d4cad7634c Merge pull request #1227 from BuhtigithuB/update-feedparser-contrib
Update feedparser.py 5.1.3 -> 5.2.1
2016-03-18 20:33:24 -05:00
mdipierro 45c28b1d76 Merge pull request #1226 from BuhtigithuB/patch-1
Delete obsolete comment left behind
2016-03-18 20:32:39 -05:00
mdipierro b978bb90de Merge pull request #1225 from BuhtigithuB/pep8-test-web
Improve test_web.py PEP8
2016-03-18 20:32:16 -05:00
mdipierro e76ecec14f Merge pull request #1224 from BuhtigithuB/pep8-html-py
Improve PEP8
2016-03-18 20:31:15 -05:00
mdipierro 8ce528327c Merge pull request #1164 from BuhtigithuB/fix/flash-w2p-flash
flash -> w2p_flash
2016-03-18 20:26:33 -05:00
mdipierro fe0e23f4b9 Merge pull request #1223 from BuhtigithuB/add-missing-helpers-test
Add missing helpers test
2016-03-18 20:25:42 -05:00
mdipierro e05da97b1d Merge branch 'master' of github.com:web2py/web2py 2016-03-18 19:38:22 -05:00
mdipierro 5290308dea global options are back 2016-03-18 19:37:46 -05:00
Christophe Varoqui 5817a1893b LDAP auth login method enhancements
1/
Allow user to pass a group mapping in manage_groups=True mode.
A group mapping is a structure like:

{
  "ldap_grp1": ["web2py_grp1", "web2py_grp2"],
  "ldap_grp2": ["ldap_grp2", "web2py_grp3"],
}

After fetching the ldap user's ldap groups, the list entries are
translated, before janitoring the auth_group and auth_membership
tables.

2/
Allow to define a callback to execute if the auth_group or
auth_membership tables are changed.

Typical use-case are cache flushing and app-driven replication.
2016-03-18 21:24:08 +01:00
Richard Vézina aa2e302936 Update feedparser.py 5.1.3 -> 5.2.1 2016-03-18 12:04:27 -04:00
Hardirc d02755eac2 add basic tests for html.py 2016-03-17 11:57:08 -04:00
BuhtigithuB d037eaab44 Delete obsolete comment left behind
Comment at L111 :
         # COMMENTED BECAUSE FAILS BUT WHY?

Seems left behind and not useful anymore
2016-03-16 16:58:24 -04:00
Richard Vézina 9aa5995924 Improve test_web.py PEP8 2016-03-16 16:43:24 -04:00
Richard Vézina a981ca52e8 Improve PEP8 2016-03-16 09:38:26 -04:00
mdipierro 327c40cd17 Merge pull request #1218 from leonelcamara/test_week2
Tests for IS_IN_DB, IS_NOT_IN_DB, IS_URL (also tests IS_HTTP_URL IS_G…
2016-03-15 11:54:25 -05:00
mdipierro f9745e8a63 Merge pull request #1217 from niphlod/fix/redis_docstrings
fixes just docstrings about usage
2016-03-15 11:53:44 -05:00
mdipierro 51ce35c4e5 Merge pull request #1216 from BuhtigithuB/improve-rocket-test
create test_rocket.py with TODO inside about pathoc tests
2016-03-15 11:53:29 -05:00
mdipierro 0f7e4d774b Merge pull request #1215 from BuhtigithuB/improve-helper-test
Improve helper test
2016-03-15 11:53:11 -05:00
Richard Vézina 6e6612a57d add a todo about remain test() which may be deleted 2016-03-15 12:41:08 -04:00
Richard Vézina 83a3149849 Order tests in test_html.py todo for missing test + add many missing tests 2016-03-15 12:40:13 -04:00
Leonel Câmara 98d33bdded Tests for IS_IN_DB, IS_NOT_IN_DB, IS_URL (also tests IS_HTTP_URL IS_GENERIC_URL) 2016-03-14 23:59:08 +00:00
mdipierro f370187332 removed double json.loads(body), thanks Simone 2016-03-14 18:03:54 -05:00
niphlod 1b15b0c6dc fixes just docstrings about usage 2016-03-14 23:32:53 +01:00
mdipierro 507c3d6c33 fixed typo 2016-03-14 15:20:08 -05:00
mdipierro e62069c8b7 another error due to removing global settings from request 2016-03-14 15:13:10 -05:00
mdipierro d94ea6b295 simplified beautify example 2016-03-14 15:08:18 -05:00
mdipierro 9533978b37 fixed error in previous commit 2016-03-14 14:57:21 -05:00
mdipierro 9706d125b4 DO NOT LEAK GLOBALS SETTINGS INTO request object 2016-03-14 14:52:41 -05:00
Richard Vézina 01aa9de919 create test_rocket.py with TODO inside about pathoc tests 2016-03-14 15:28:50 -04:00
Richard Vézina 55994c489b Improve test naming convention test_ 2016-03-14 15:09:33 -04:00
Richard Vézina 983627daa4 Improve PEP8 test_html.py 2016-03-14 15:02:40 -04:00
mdipierro 7c299936e4 plugins link broken 2016-03-14 13:57:59 -05:00
mdipierro df55f52d8f fixed #1187 Field(.)Virtual typo, thanks RekGRpth 2016-03-14 12:45:01 -05:00
mdipierro c81f1fd6c8 reverting previous commit 2016-03-14 12:34:09 -05:00
mdipierro f15dd4b6e5 fixed #1204, updating session when add_membership 2016-03-14 12:32:34 -05:00
mdipierro e9e61cbca4 fixed #1213, custom password field name 2016-03-14 12:27:37 -05:00
mdipierro 700821e372 Merge pull request #1209 from zvolsky/transl_update_script
new script to update untranslated messages from other language file
2016-03-14 12:18:05 -05:00
mdipierro 1d04f8837e resorted tests as suggested by BuhtigithuB 2016-03-14 12:16:52 -05:00
mdipierro a375e047e9 Leonel new tests gluon/tests/test_validators.py 2016-03-14 12:04:47 -05:00
mdipierro fe6b222aaf Merge pull request #1212 from BuhtigithuB/improve-validators-test
Order tests to match validators.py and flag missing tests
2016-03-14 11:54:18 -05:00
mdipierro 0b5bb9b996 Merge pull request #1210 from zvolsky/nginx_uwsgi
nginx/ubuntu deploy script: added uWSGI Emperor configuration
2016-03-14 11:52:49 -05:00
mdipierro be4df0dee7 Merge pull request #1208 from zvolsky/czech_translation
updated czech translation (in admin and welcome)
2016-03-14 11:51:10 -05:00
mdipierro d877e8b6d0 Merge pull request #1207 from niphlod/fix/redis_scheduler
fixes the same bugs reported in #1191 for the "standard" scheduler
2016-03-14 11:50:49 -05:00
Richard Vézina 2531c2c640 Improve PEP8 2016-03-10 22:09:38 -05:00
Richard Vézina eca300af32 Order tests to match validators.py and flag missing tests 2016-03-10 21:56:46 -05:00
zvolsky 43c60df371 nginx/ubuntu deploy script: added uWSGI Emperor configuration 2016-03-10 10:20:36 +01:00
zvolsky cdac608efc new script to update untranslated messages from other language file 2016-03-10 09:48:04 +01:00
zvolsky 3311486b14 updated czech translation (in admin and welcome) 2016-03-10 09:35:18 +01:00
niphlod 038d0d17a4 fixes the same bugs reported in #1191 for the "standard" scheduler 2016-03-10 00:01:39 +01:00
mdipierro 3999fd80f8 removed unwanted icons 2016-03-09 12:41:12 -06:00
mdipierro 2db3975a32 social icons 2016-03-09 12:37:15 -06:00
mdipierro ebe3434a86 fixed commit error 2016-03-09 11:54:27 -06:00
Richard Vézina dc1c85928d flash -> w2p_flash 2016-01-08 13:13:13 -05:00
113 changed files with 8407 additions and 7994 deletions
+14
View File
@@ -12,8 +12,22 @@ python:
- 'pypy'
install:
- |
if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then
export PYENV_ROOT="$HOME/.pyenv"
if [ -f "$PYENV_ROOT/bin/pyenv" ]; then
pushd "$PYENV_ROOT" && git pull && popd
else
rm -rf "$PYENV_ROOT" && git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT"
fi
export PYPY_VERSION="5.0.1"
"$PYENV_ROOT/bin/pyenv" install --skip-existing "pypy-$PYPY_VERSION"
virtualenv --python="$PYENV_ROOT/versions/pypy-$PYPY_VERSION/bin/python" "$HOME/virtualenvs/pypy-$PYPY_VERSION"
source "$HOME/virtualenvs/pypy-$PYPY_VERSION/bin/activate"
fi
- pip install -e .
before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
+31 -4
View File
@@ -1,10 +1,35 @@
## trunk
## 2.14.6
- Increased test coverage (thanks Richard)
- Fixed some newly discovered security issues in admin:
CSRF vulnerability in admin that allows disabling apps
Brute force password attack vulnerability in admin
(thanks Narendra and Leonel)
## 2.14.1-5
- fixed two major security issues that caused the examples app to leak information
- new Auth(…,host_names=[…]) to prevent host header injection
- improved scheduler
- pep8 enhancements
- many bug fixes
- restored GAE support that was broken in 2.13.*
- improved fabfile for deployment
- refactored examples with stupid.css
- new JWT implementation (experimental)
- new gluon.contrib.redis_scheduler
- BREAKING: changes to gluon.contrib.redis_cache
- myconf.get
- LDAP groups (experimental)
- .flash -> .w2p_flash
- Updated feedparser.py 5.2.1
- Updated jQuery 1.12.2
- welcome app now checks for version number
- Redis improvements. New syntax:
BEFORE:
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
NOW:
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_cache import RedisCache
@@ -14,11 +39,12 @@
# socket_connect_timeout=None, .....)
# exactly as a redis.StrictRedis instance
cache.redis = RedisCache(redis_conn=rconn, debug=True)
- BREAKING: changes to gluon.contrib.redis_session
BEFORE:
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
session.connect(request, response, db = sessiondb)
NOW:
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_session import RedisSession
@@ -26,8 +52,9 @@
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
session.connect(request, response, db = sessiondb)
Many thanks to Richard and Simone for their work and dedication.
## 2.13.1-2
## 2.13.*
- fixed a security issue in request_reset_password
- added fabfile.py
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.13.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.13.4-stable+timestamp.2016.02.10.15.41.11
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48
+134 -109
View File
@@ -32,15 +32,15 @@ from gluon.languages import (read_possible_languages, read_dict, write_dict,
if DEMO_MODE and request.function in ['change_password', 'pack',
'pack_custom','pack_plugin', 'upgrade_web2py', 'uninstall',
'cleanup', 'compile_app', 'remove_compiled_app', 'delete',
'delete_plugin', 'create_file', 'upload_file', 'update_languages',
'reload_routes', 'git_push', 'git_pull', 'install_plugin']:
'pack_custom', 'pack_plugin', 'upgrade_web2py', 'uninstall',
'cleanup', 'compile_app', 'remove_compiled_app', 'delete',
'delete_plugin', 'create_file', 'upload_file', 'update_languages',
'reload_routes', 'git_push', 'git_pull', 'install_plugin']:
session.flash = T('disabled in demo mode')
redirect(URL('site'))
if is_gae and request.function in ('edit', 'edit_language',
'edit_plurals', 'update_languages', 'create_file', 'install_plugin'):
'edit_plurals', 'update_languages', 'create_file', 'install_plugin'):
session.flash = T('disabled in GAE mode')
redirect(URL('site'))
@@ -74,8 +74,10 @@ def log_progress(app, mode='EDIT', filename=None, progress=0):
def safe_open(a, b):
if (DEMO_MODE or is_gae) and ('w' in b or 'a' in b):
class tmp:
def write(self, data):
pass
def close(self):
pass
return tmp()
@@ -119,6 +121,9 @@ def index():
send = URL('site')
if session.authorized:
redirect(send)
elif failed_login_count() >= allowed_number_of_attempts:
time.sleep(2 ** allowed_number_of_attempts)
raise HTTP(403)
elif request.vars.password:
if verify_password(request.vars.password[:1024]):
session.authorized = True
@@ -208,6 +213,7 @@ def site():
file_or_appurl = 'file' in request.vars or 'appurl' in request.vars
class IS_VALID_APPNAME(object):
def __call__(self, value):
if not re.compile('^\w+$').match(value):
return (value, T('Invalid application name'))
@@ -268,7 +274,7 @@ def site():
raise Exception("404 file not found")
except Exception, e:
session.flash = \
DIV(T('Unable to download app because:'), PRE(str(e)))
DIV(T('Unable to download app because:'), PRE(repr(e)))
redirect(URL(r=request))
fname = form_update.vars.url
@@ -325,7 +331,7 @@ def report_progress(app):
if not m:
continue
days = -(request.now - datetime.datetime.strptime(m[0],
'%Y-%m-%d %H:%M:%S')).days
'%Y-%m-%d %H:%M:%S')).days
counter += int(m[1])
events.append([days, counter])
return events
@@ -353,6 +359,7 @@ def pack():
session.flash = T('internal error: %s', e)
redirect(URL('site'))
def pack_plugin():
app = get_app()
if len(request.args) == 2:
@@ -368,7 +375,6 @@ def pack_plugin():
redirect(URL('plugin', args=request.args))
def pack_exe(app, base, filenames=None):
import urllib
import zipfile
@@ -397,10 +403,20 @@ def pack_exe(app, base, filenames=None):
def pack_custom():
app = get_app()
base = apath(app, r=request)
def ignore(fs):
return [f for f in fs if not (
f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))]
files = {}
for (r, d, f) in os.walk(base):
files[r] = {'folders': ignore(d), 'files': ignore(f)}
if request.post_vars.file:
valid_set = set(os.path.relpath(os.path.join(r, f), base) for r in files for f in files[r]['files'])
files = request.post_vars.file
files = [files] if not isinstance(files,list) else files
files = [files] if not isinstance(files, list) else files
files = [file for file in files if file in valid_set]
if request.post_vars.doexe is None:
fname = 'web2py.app.%s.w2p' % app
try:
@@ -417,12 +433,7 @@ def pack_custom():
redirect(URL(args=request.args))
else:
return pack_exe(app, base, files)
def ignore(fs):
return [f for f in fs if not (
f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))]
files = {}
for (r,d,f) in os.walk(base):
files[r] = {'folders':ignore(d),'files':ignore(f)}
return locals()
@@ -485,14 +496,14 @@ def cleanup():
def compile_app():
app = get_app()
c = app_compile(app, request,
skip_failed_views = (request.args(1) == 'skip_failed_views'))
skip_failed_views=(request.args(1) == 'skip_failed_views'))
if not c:
session.flash = T('application compiled')
elif isinstance(c, list):
session.flash = DIV(*[T('application compiled'), BR(), BR(),
T('WARNING: The following views could not be compiled:'), BR()] +
[CAT(BR(), view) for view in c] +
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
[CAT(BR(), view) for view in c] +
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
else:
session.flash = DIV(T('Cannot compile: there are errors in your app:'),
CODE(c))
@@ -533,8 +544,8 @@ def delete():
redirect(URL(sender, anchor=request.vars.id2))
return dict(dialog=dialog, filename=filename)
def enable():
if not URL.verify(request, hmac_key=session.hmac_key): raise HTTP(401)
app = get_app()
filename = os.path.join(apath(app, r=request), 'DISABLED')
if is_gae:
@@ -546,6 +557,7 @@ def enable():
safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now)
return SPAN(T('Enable'), _style='color:red')
def peek():
""" Visualize object code """
app = get_app(request.vars.app)
@@ -609,7 +621,7 @@ def edit():
# Load json only if it is ajax edited...
app = get_app(request.vars.app)
app_path = apath(app, r=request)
preferences={'theme':'web2py', 'editor': 'default', 'closetag': 'true', 'codefolding': 'false', 'tabwidth':'4', 'indentwithtabs':'false', 'linenumbers':'true', 'highlightline':'true'}
preferences = {'theme': 'web2py', 'editor': 'default', 'closetag': 'true', 'codefolding': 'false', 'tabwidth': '4', 'indentwithtabs': 'false', 'linenumbers': 'true', 'highlightline': 'true'}
config = Config(os.path.join(request.folder, 'settings.cfg'),
section='editor', default_values={})
preferences.update(config.read())
@@ -617,14 +629,14 @@ def edit():
if not(request.ajax) and not(is_mobile):
# return the scaffolding, the rest will be through ajax requests
response.title = T('Editing %s') % app
return response.render ('default/edit.html', dict(app=app, editor_settings=preferences))
return response.render('default/edit.html', dict(app=app, editor_settings=preferences))
# show settings tab and save prefernces
if 'settings' in request.vars:
if request.post_vars: #save new preferences
if request.post_vars: # save new preferences
post_vars = request.post_vars.items()
# Since unchecked checkbox are not serialized, we must set them as false by hand to store the correct preference in the settings
post_vars+= [(opt, 'false') for opt in preferences if opt not in request.post_vars ]
post_vars += [(opt, 'false') for opt in preferences if opt not in request.post_vars]
if config.save(post_vars):
response.headers["web2py-component-flash"] = T('Preferences saved correctly')
else:
@@ -632,8 +644,8 @@ def edit():
response.headers["web2py-component-command"] = "update_editor(%s);$('a[href=#editor_settings] button.close').click();" % response.json(config.read())
return
else:
details = {'realfilename':'settings', 'filename':'settings', 'id':'editor_settings', 'force': False}
details['plain_html'] = response.render('default/editor_settings.html', {'editor_settings':preferences})
details = {'realfilename': 'settings', 'filename': 'settings', 'id': 'editor_settings', 'force': False}
details['plain_html'] = response.render('default/editor_settings.html', {'editor_settings': preferences})
return response.json(details)
""" File edit handler """
@@ -740,7 +752,7 @@ def edit():
B(ex_name), ' ' + T('at line %s', e.lineno),
offset and ' ' +
T('at char %s', offset) or '',
PRE(str(e)))
PRE(repr(e)))
if data_or_revert and request.args[1] == 'modules':
# Lets try to reload the modules
try:
@@ -751,7 +763,7 @@ def edit():
% (request.args[0], mopath)])
except Exception, e:
response.flash = DIV(
T('failed to reload module because:'), PRE(str(e)))
T('failed to reload module because:'), PRE(repr(e)))
edit_controller = None
editviewlinks = None
@@ -764,8 +776,8 @@ def edit():
view = request.args[3].replace('.html', '')
view_link = URL(request.args[0], request.args[2], view)
elif filetype == 'python' and request.args[1] == 'controllers':
## it's a controller file.
## Create links to all of the associated view files.
# it's a controller file.
# Create links to all of the associated view files.
app = get_app()
viewname = os.path.splitext(request.args[2])[0]
viewpath = os.path.join(app, 'views', viewname)
@@ -796,22 +808,22 @@ def edit():
return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight})
else:
file_details = dict(app=request.args[0],
lineno=request.vars.lineno or 1,
editor_settings=preferences,
filename=filename,
realfilename=realfilename,
filetype=filetype,
data=data,
edit_controller=edit_controller,
file_hash=file_hash,
saved_on=saved_on,
controller=controller,
functions=functions,
view_link=view_link,
editviewlinks=editviewlinks,
id=IS_SLUG()(filename)[0],
force= True if (request.vars.restore or
request.vars.revert) else False)
lineno=request.vars.lineno or 1,
editor_settings=preferences,
filename=filename,
realfilename=realfilename,
filetype=filetype,
data=data,
edit_controller=edit_controller,
file_hash=file_hash,
saved_on=saved_on,
controller=controller,
functions=functions,
view_link=view_link,
editviewlinks=editviewlinks,
id=IS_SLUG()(filename)[0],
force=True if (request.vars.restore or
request.vars.revert) else False)
plain_html = response.render('default/edit_js.html', file_details)
file_details['plain_html'] = plain_html
if is_mobile:
@@ -820,14 +832,16 @@ def edit():
else:
return response.json(file_details)
def todolist():
""" Returns all TODO of the requested app
"""
app = request.vars.app or ''
app_path = apath('%(app)s' % {'app':app}, r=request)
dirs=['models', 'controllers', 'modules', 'private' ]
app_path = apath('%(app)s' % {'app': app}, r=request)
dirs = ['models', 'controllers', 'modules', 'private']
def listfiles(app, dir, regexp='.*\.py$'):
files = sorted( listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
files = sorted(listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp))
files = [x.replace(os.path.sep, '/') for x in files if not x.endswith('.bak')]
return files
@@ -838,17 +852,18 @@ def todolist():
for d in dirs:
for f in listfiles(app, d):
matches = []
filename= apath(os.path.join(app, d, f), r=request)
filename = apath(os.path.join(app, d, f), r=request)
with open(filename, 'r') as f_s:
src = f_s.read()
for m in regex.finditer(src):
start = m.start()
lineno = src.count('\n', 0, start) + 1
matches.append({'text':m.group(0), 'lineno':lineno})
matches.append({'text': m.group(0), 'lineno': lineno})
if len(matches) != 0:
output.append({'filename':f,'matches':matches, 'dir':d})
output.append({'filename': f, 'matches': matches, 'dir': d})
return {'todo': output, 'app': app}
return {'todo':output, 'app': app}
def editor_sessions():
config = Config(os.path.join(request.folder, 'settings.cfg'),
@@ -858,13 +873,14 @@ def editor_sessions():
if request.vars.session_name and request.vars.files:
session_name = request.vars.session_name
files = request.vars.files
preferences.update({session_name:','.join(files)})
preferences.update({session_name: ','.join(files)})
if config.save(preferences.items()):
response.headers["web2py-component-flash"] = T('Session saved correctly')
else:
response.headers["web2py-component-flash"] = T('Session saved on session only')
return response.render('default/editor_sessions.html', {'editor_sessions':preferences})
return response.render('default/editor_sessions.html', {'editor_sessions': preferences})
def resolve():
"""
@@ -901,8 +917,8 @@ def resolve():
def getclass(item):
""" Determine item class """
operators = {' ':'normal', '+':'plus', '-':'minus'}
operators = {' ': 'normal', '+': 'plus', '-': 'minus'}
return operators[item[0]]
if request.vars:
@@ -921,7 +937,7 @@ def resolve():
diff = TABLE(*[TR(TD(gen_data(i, item)),
TD(item[0]),
TD(leading(item[2:]),
TT(item[2:].rstrip())),
TT(item[2:].rstrip())),
_class=getclass(item))
for (i, item) in enumerate(d) if item[0] != '?'])
@@ -968,11 +984,11 @@ def edit_language():
new_row = DIV(LABEL(prefix, k, _style="font-weight:normal;"),
CAT(elem, '\n', TAG.BUTTON(
T('delete'),
_onclick='return delkey("%s")' % name,
_class='btn')), _id=name, _class='span6 well well-small')
T('delete'),
_onclick='return delkey("%s")' % name,
_class='btn')), _id=name, _class='span6 well well-small')
rows.append(DIV(new_row,_class="row-fluid"))
rows.append(DIV(new_row, _class="row-fluid"))
rows.append(DIV(INPUT(_type='submit', _value=T('update'), _class="btn btn-primary"), _class='controls'))
form = FORM(*rows)
if form.accepts(request.vars, keepvalues=True):
@@ -1128,18 +1144,18 @@ def design():
# Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*',
maxnum = MAXNFILES)
maxnum=MAXNFILES)
statics = [x.replace(os.path.sep, '/') for x in statics]
statics.sort()
# Get all languages
langpath = os.path.join(apath(app, r=request),'languages')
langpath = os.path.join(apath(app, r=request), 'languages')
languages = dict([(lang, info) for lang, info
in read_possible_languages(langpath).iteritems()
if info[2] != 0]) # info[2] is langfile_mtime:
# get only existed files
# get only existed files
#Get crontab
# Get crontab
cronfolder = apath('%s/cron' % app, r=request)
crontab = apath('%s/cron/crontab' % app, r=request)
if not is_gae:
@@ -1265,7 +1281,7 @@ def plugin():
# Get all static files
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*',
maxnum = MAXNFILES)
maxnum=MAXNFILES)
statics = [x.replace(os.path.sep, '/') for x in statics]
statics.sort()
@@ -1273,9 +1289,9 @@ def plugin():
languages = sorted([lang + '.py' for lang, info in
T.get_possible_languages_info().iteritems()
if info[2] != 0]) # info[2] is langfile_mtime:
# get only existed files
# get only existed files
#Get crontab
# Get crontab
crontab = apath('%s/cron/crontab' % app, r=request)
if not os.path.exists(crontab):
safe_write(crontab, '#crontab')
@@ -1298,6 +1314,7 @@ def plugin():
languages=languages,
crontab=crontab)
def create_file():
""" Create files handler """
if request.vars and not request.vars.token == session.token:
@@ -1309,7 +1326,7 @@ def create_file():
path = abspath(request.vars.location)
else:
if request.vars.dir:
request.vars.location += request.vars.dir + '/'
request.vars.location += request.vars.dir + '/'
app = get_app(name=request.vars.location.split('/')[0])
path = apath(request.vars.location, r=request)
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
@@ -1419,7 +1436,7 @@ def create_file():
elif (path[-8:] == '/static/') or (path[-9:] == '/private/'):
if (request.vars.plugin and
not filename.startswith('plugin_%s/' % request.vars.plugin)):
not filename.startswith('plugin_%s/' % request.vars.plugin)):
filename = 'plugin_%s/%s' % (request.vars.plugin, filename)
text = ''
@@ -1439,17 +1456,17 @@ def create_file():
log_progress(app, 'CREATE', filename)
if request.vars.dir:
result = T('file "%(filename)s" created',
dict(filename=full_filename[len(path):]))
dict(filename=full_filename[len(path):]))
else:
session.flash = T('file "%(filename)s" created',
dict(filename=full_filename[len(path):]))
dict(filename=full_filename[len(path):]))
vars = {}
if request.vars.id:
vars['id'] = request.vars.id
if request.vars.app:
vars['app'] = request.vars.app
redirect(URL('edit',
args=[os.path.join(request.vars.location, filename)], vars=vars))
args=[os.path.join(request.vars.location, filename)], vars=vars))
except Exception, e:
if not isinstance(e, HTTP):
@@ -1460,7 +1477,7 @@ def create_file():
response.headers['web2py-component-content'] = 'append'
response.headers['web2py-component-command'] = "%s %s %s" % (
"$.web2py.invalidate('#files_menu');",
"load_file('%s');" % URL('edit', args=[app,request.vars.dir,filename]),
"load_file('%s');" % URL('edit', args=[app, request.vars.dir, filename]),
"$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));")
return ''
else:
@@ -1468,32 +1485,35 @@ def create_file():
def listfiles(app, dir, regexp='.*\.py$'):
files = sorted(
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
return files
files = sorted(
listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp))
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
return files
def editfile(path, file, vars={}, app=None):
args = (path, file) if 'app' in vars else (app, path, file)
url = URL('edit', args=args, vars=vars)
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
def editfile(path,file,vars={}, app = None):
args=(path,file) if 'app' in vars else (app,path,file)
url = URL('edit', args=args, vars=vars)
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
def files_menu():
app = request.vars.app or 'welcome'
dirs=[{'name':'models', 'reg':'.*\.py$'},
{'name':'controllers', 'reg':'.*\.py$'},
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
{'name':'modules', 'reg':'.*\.py$'},
{'name':'static', 'reg': '[^\.#].*'},
{'name':'private', 'reg':'.*\.py$'}]
result_files = []
for dir in dirs:
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
_class="nav nav-list small-font"),
_id=dir['name'] + '_files', _style="display: none;")))
return dict(result_files = result_files)
app = request.vars.app or 'welcome'
dirs = [{'name': 'models', 'reg': '.*\.py$'},
{'name': 'controllers', 'reg': '.*\.py$'},
{'name': 'views', 'reg': '[\w/\-]+(\.\w+)+$'},
{'name': 'modules', 'reg': '.*\.py$'},
{'name': 'static', 'reg': '[^\.#].*'},
{'name': 'private', 'reg': '.*\.py$'}]
result_files = []
for dir in dirs:
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.', '__')), app), _style="overflow:hidden", _id=dir['name'] + "__" + f.replace('.', '__'))
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
_class="nav nav-list small-font"),
_id=dir['name'] + '_files', _style="display: none;")))
return dict(result_files=result_files)
def upload_file():
""" File uploading handler """
@@ -1556,7 +1576,7 @@ def errors():
app = get_app()
if is_gae:
method = 'dbold' if ('old' in
(request.args(1) or '')) else 'dbnew'
(request.args(1) or '')) else 'dbnew'
else:
method = request.args(1) or 'new'
db_ready = {}
@@ -1599,7 +1619,7 @@ def errors():
hash2error[hash]['count'] += 1
except KeyError:
error_lines = error['traceback'].split("\n")
last_line = error_lines[-2] if len(error_lines)>1 else 'unknown'
last_line = error_lines[-2] if len(error_lines) > 1 else 'unknown'
error_causer = os.path.split(error['layer'])[1]
hash2error[hash] = dict(count=1, pickel=error,
causer=error_causer,
@@ -1638,9 +1658,9 @@ def errors():
last_line = error_lines[-2]
error_causer = os.path.split(error['layer'])[1]
hash2error[hash] = dict(count=1,
pickel=error, causer=error_causer,
last_line=last_line, hash=hash,
ticket=fn.ticket_id)
pickel=error, causer=error_causer,
last_line=last_line, hash=hash,
ticket=fn.ticket_id)
except AttributeError, e:
tk_db(tk_table.id == fn.id).delete()
tk_db.commit()
@@ -1657,11 +1677,11 @@ def errors():
tk_db(tk_table.ticket_id == item[7:]).delete()
tk_db.commit()
tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id,
tk_table.created_datetime,
orderby=~tk_table.created_datetime)
tk_table.created_datetime,
orderby=~tk_table.created_datetime)
tickets = [row.ticket_id for row in tickets_]
times = dict([(row.ticket_id, row.created_datetime) for
row in tickets_])
row in tickets_])
return dict(app=app, tickets=tickets, method=method,
times=times, db_ready=db_ready)
@@ -1721,7 +1741,7 @@ def make_link(path):
if ext.lower() == editable[key] and check_extension:
return A('"' + tryFile + '"',
_href=URL(r=request,
f='edit/%s/%s/%s' % (app, key, filename))).xml()
f='edit/%s/%s/%s' % (app, key, filename))).xml()
return ''
@@ -1867,7 +1887,7 @@ def bulk_register():
redirect(URL('site'))
return locals()
### Begin experimental stuff need fixes:
# Begin experimental stuff need fixes:
# 1) should run in its own process - cannot os.chdir
# 2) should not prompt user at console
# 3) should give option to force commit and not reuqire manual merge
@@ -1934,6 +1954,7 @@ def git_push():
redirect(URL('site'))
return dict(app=app, form=form)
def plugins():
app = request.args(0)
from serializers import loads_json
@@ -1948,12 +1969,16 @@ def plugins():
session.plugins = []
return dict(plugins=session.plugins["results"], app=request.args(0))
def install_plugin():
app = request.args(0)
source = request.vars.source
plugin = request.vars.plugin
if not (source and app):
raise HTTP(500, T("Invalid request"))
# make sure no XSS attacks in source
if not source.lower().split('://')[0] in ('http','https'):
raise HTTP(500, T("Invalid request"))
form = SQLFORM.factory()
result = None
if form.process().accepted:
@@ -1969,5 +1994,5 @@ def install_plugin():
else:
session.flash = \
T('unable to install plugin "%s"', filename)
redirect(URL(f="plugins", args=[app,]))
redirect(URL(f="plugins", args=[app, ]))
return dict(form=form, app=app, plugin=plugin, source=source)
+340 -104
View File
@@ -2,89 +2,131 @@
{
'!langcode!': 'cs-cz',
'!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". (Avšak výsledky databázového JOINu nelze mazat ani upravovat.)',
'"User Exception" debug mode. ': '"Uživatelská výjimka", Debug mód.',
'"User Exception" debug mode. An error ticket could be issued!': '"Uživatelská výjimka", Debug mód. Může být vystaven chybový tiket.',
'%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}',
'%s': '%s',
'%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených',
'%s students registered': '%s studentů registrováno',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(requires internet access, experimental)',
'(requires internet access, experimental)': '(vyžaduje internetové připojení, experimentální)',
'(something like "it-it")': '(například "cs-cs")',
'(version %s)': '(verze %s)',
'?': '?',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}',
'Abort': 'Ukončit',
'About': 'O programu',
'About application': 'O aplikaci',
'Accept Terms': 'Souhlasit s podmínkami',
'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci',
'Admin design page': 'Admin design page',
'Additional code for your application': 'Další kód pro Vaši aplikaci (pro příkaz import). Neběží ve specifickém režimu ani ve vláknech jako model/kontrolér/šablona, ale jako standardní python moduly. Ty tedy můžete umístit sem (pouze pro tuto aplikaci) nebo používat systémově dostupné.',
'Admin design page': 'Admin design stránka',
'admin disabled because no admin password': 'admin je zakázán, protože chybí heslo administrátora',
'admin disabled because not supported on google app engine': 'admin je zakázán kvůli chybějící podpoře na Google App Engine',
'admin disabled because too many invalid login attempts': 'Admin je zakázán po příliš mnoha nesprávných pokusech o přihlášení',
'admin disabled because unable to access password file': 'Admin je zakázán, protože nelze číst soubor s heslem',
'Admin is disabled because insecure channel': 'Admin je zakázán na nezabezpečeném připojení',
'Admin language': 'jazyk rozhraní',
'Admin versioning page': 'Admin verzovací stránka',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'An error occured, please %s the page',
'An error occured, please %s the page': 'Došlo k chybě, prosím %s stránku',
'and rename it:': 'a přejmenovat na:',
'App does not exist or you are not authorized': 'Aplikace neexistuje nebo vám chybí oprávnění',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Application',
'Application': 'Aplikace',
'application "%s" uninstalled': 'application "%s" odinstalována',
'Application cannot be generated in demo mode': 'Aplikace nemůže být vytvořena v demo módu',
'application compiled': 'aplikace zkompilována',
'Application exists already': 'Aplikace již existuje',
'application is compiled and cannot be designed': 'aplikace je přeložena a nelze ji editovat',
'Application name:': 'Název aplikace:',
'Application updated via git pull': 'Aplikace byla aktualizována pomocí git pull',
'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete file "%s"?': 'Skutečně chcete smazat soubor "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Skutečně chcete smazat plugin "%s"?',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'arguments': 'arguments',
'at char %s': 'at char %s',
'at line %s': 'at line %s',
'ATTENTION:': 'ATTENTION:',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'Are you sure?': 'Jste si jist(a)?',
'arguments': 'argumenty',
'at char %s': 'na pozici znaku %s',
'at line %s': 'na řádku %s',
'ATTENTION:': 'POZOR:',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'POZOR: Přihlášení vyžaduje zabezpečené (HTTPS) připojení nebo spouštění na localhost.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'POZOR: TESTOVÁNÍ NENÍ BEZPEČNÉ PŘI SOUBĚŽNÝCH VLÁKNECH. NESPOUŠTĚJ VÍCE TESTŮ SOUBĚŽNĚ.',
'ATTENTION: you cannot edit the running application!': 'POZOR: Nelze editovat spuštěnou aplikaci.',
'Autocomplete Python Code': 'Autocomplete Python kód',
'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět',
'Back to wizard': 'Back to wizard',
'Basics': 'Basics',
'Back to the plugins list': 'Zpět do seznamu pluginů',
'Back to wizard': 'Zpátky do průvodce',
'Basics': 'Základy',
'Begin': 'Začít',
'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení',
'Bulk Register': 'Hromadná registrace',
'Bulk Student Registration': 'Hromadná registrace studentů',
'Buy this book': 'Koupit web2py knihu',
'Cache': 'Cache',
'cache': 'cache',
'Cache Cleared': 'Cache byla vymazána',
'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo',
'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné',
'Cannot compile: there are errors in your app:': 'Nelze zkompilovat: ve vaší aplikaci jsou chyby:',
'cannot create file': 'nelze vytvořit soubor',
'cannot upload file "%(filename)s"': 'nelze nahrát soubor "%(filename)s"',
'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací',
'change editor settings': 'změnit nastavení editoru',
'Change password': 'Změna hesla',
'Changelog': 'Žurnál změn',
'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit',
'Clear': 'Inicializovat',
'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta',
'code': 'code',
'Code listing': 'Code listing',
'code': 'kód',
'Code listing': 'Výpis kódu',
'collapse/expand all': 'vše sbalit/rozbalit',
'Command': 'Příkaz',
'Comment:': 'Komentář:',
'Commit': 'Potvrdit',
'Commit form': 'Potvrdit formulář',
'Committed files': 'Potvrzené soubory',
'Community': 'Komunita',
'Compile': 'Zkompilovat',
'Compile (all or nothing)': 'Přeložit (vše nebo nic)',
'Compile (skip failed views)': 'Přeložit (přeskočit chybné šablony)',
'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka',
'continue': 'continue',
'continue': 'pokračovat',
'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry',
'controllers': 'kontroléry',
@@ -92,9 +134,12 @@
'Count': 'Počet',
'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:',
'Create/Upload': 'Vytvořit/Nahrát',
'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým',
'Created by:': 'Vytvořil:',
'Created On': 'Vytvořeno - kdy',
'Created on:': 'Vytvořeno:',
'crontab': 'crontab',
'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď',
@@ -105,18 +150,19 @@
'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr',
'Database administration': 'Database administration',
'Database administration': 'Administrace databáze',
'database administration': 'správa databáze',
'Database Administration (appadmin)': 'Administrace databáze (appadmin)',
'Date and Time': 'Datum a čas',
'day': 'den',
'db': 'db',
'DB Model': 'Databázový model',
'Debug': 'Ladění',
'defines tables': 'defines tables',
'defines tables': 'definuje tabulky',
'Delete': 'Smazat',
'delete': 'smazat',
'delete all checked': 'smazat vše označené',
'delete plugin': 'delete plugin',
'delete plugin': 'zrušit plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení',
@@ -124,166 +170,265 @@
'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deploy to pythonanywhere': 'Nahrát na PythonAnywhere',
'Deploy to PythonAnywhere': 'Nahrát na PythonAnywhere',
'Deployment form': 'Forumlář pro deployment (nasazení)',
'Deployment Interface': 'Rozhraní pro deployment (nasazení)',
'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis',
'Description:': 'Popis:',
'design': 'návrh',
'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti',
'direction: ltr': 'směr: ltr',
'directory not found': 'adresář nebyl nalezen',
'Disable': 'Zablokovat',
'Disabled': 'Blokováno',
'disabled in demo mode': 'zakázáno v demo módu',
'disabled in GAE mode': 'zakázáno v GAE módu',
'disabled in multi user mode': 'zakázáno ve víceuživatelském módu',
'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán',
'Display line numbers': 'Zobrazit čísla řádků',
'DO NOT use the "Pack compiled" feature.': 'NEPOUŽÍVEJ vlastnost "Zabalit zkompilované".',
'docs': 'dokumentace',
'Docs': 'Dokumentace',
'Documentation': 'Dokumentace',
"Don't know what to do?": 'Nevíte kudy kam?',
'done!': 'hotovo!',
'Downgrade': 'Downgrade (vrácení verze)',
'Download': 'Stáhnout',
'Download .w2p': 'Stažení .w2p',
'Download as .exe': 'Stáhnout jako .exe',
'download layouts': 'stáhnout moduly rozvržení stránky',
'Download layouts from repository': 'Stáhnout moduly rozvržení z repozitáře',
'download plugins': 'stáhnout zásuvné moduly',
'Download plugins from repository': 'Stáhnout pluginy z repozitáře',
'E-mail': 'E-mail',
'Edit': 'Upravit',
'edit all': 'edit all',
'edit all': 'editovat vše',
'Edit application': 'Správa aplikace',
'edit controller': 'edit controller',
'edit controller': 'editovat controller',
'edit controller:': 'editovat kontrolér:',
'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil',
'edit views:': 'upravit pohled:',
'edit views:': 'upravit šablonu (view):',
'Editing %s': 'Editace %s',
'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editing Plural Forms File',
'Editing Plural Forms File': 'Editování souboru množných čísel',
'Editor': 'Editor',
'Email Address': 'Emailová adresa',
'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat',
'Enable Close-Tag': 'Povolit Close-Tag',
'Enable Code Folding': 'Povolit sdružování kódu',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Ticket chyby',
'Error ticket': 'Tiket chyby',
'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Exception %s': 'Exception %s',
'Exception %(extype)s: %(exvalue)s': 'Výjimka %(extype)s: %(exvalue)s',
'Exception %s': 'Výjimka %s',
'Exception instance attributes': 'Prvky instance výjimky',
'Expand Abbreviation': 'Expand Abbreviation',
'Exit Fullscreen': 'Ukončit režim celé obrazovky',
'Expand Abbreviation': 'Rozvinout zkratku',
'Expand Abbreviation (html files only)': 'Rozvinout zkratku (pouze html soubory)',
'export as csv file': 'exportovat do .csv souboru',
'Exports:': 'Exporty:',
'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'failed to reload module because:': 'nepodařilo se restartovat modul, protože:',
'FAQ': 'Často kladené dotazy',
'File': 'Soubor',
'file': 'soubor',
'file "%(filename)s" created': 'file "%(filename)s" created',
'file "%(filename)s" created': 'soubor "%(filename)s" byl vytvořen',
'file "%(filename)s" deleted': 'soubor "%(filename)s" byl zrušen',
'file "%(filename)s" uploaded': 'soubor "%(filename)s" byl nahrán',
'file "%s" of %s restored': 'soubor "%s" z %s byl obnoven',
'file changed on disk': 'soubor se na disku změnil',
'file does not exist': 'soubor neexistuje',
'file not found': 'soubor nebyl nalezen',
'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s',
'filename': 'jméno souboru',
'Filename': 'Název souboru',
'Files added': 'Soubory byly přidány',
'filter': 'filtr',
'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Form has errors': 'Ve formuláři jsou chyby',
'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Frames',
'Frames': 'Framy',
'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Functions with no doctests will result in [passed] tests.': 'Funkce bez doctestů se projeví jako [úspěšný] test.',
'GAE Email': 'GAE e-mail',
'GAE Output': 'GAE výstup',
'GAE Password': 'GAE heslo',
'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné',
'go!': 'OK!',
'Goto': 'Goto',
'graph model': 'graph model',
'Google App Engine Deployment Interface': 'Google App Engine - rozhraní pro nasazení',
'Google Application Id': 'ID Google Aplikace',
'Goto': 'Přejít na',
'graph model': 'grafický model',
'Graph Model': 'Grafický model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny',
'Groups': 'Skupiny',
'Hello World': 'Ahoj světe',
'Help': 'Nápověda',
'here': 'zde',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Highlight current line': 'Zvýraznit aktuální řádek',
'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak jste se sem vlastně dostal?',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'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.',
'If start the downgrade, be patient, it may take a while to rollback': 'Spustíte-li downgrade verze, vyčkejte, protože vrácení změn může trvat dlouho',
'If start the upgrade, be patient, it may take a while to download': 'Spustíte-li upgrade, vyčkejte, protože stahování může trvat dlouho',
'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.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Jestliže přehled výše obsahuje číslo chybového tiketu, znamená to chybu v kontroléru, ještě před pokusem vykonat doctesty. (Často je to způsobeno chybou odsazení nebo chybou mimo kód funkce.) Zelený nadpis označuje, že žádný test nehavaroval. (V tom případě dílčí výsledky nejsou uvedeny.)',
'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.': 'Jestliže přehled výše obsahuje číslo chybového tiketu, znamená to chybu v kontroléru, ještě před pokusem vykonat doctesty. (Často je to způsobeno chybou odsazení nebo chybou mimo kód funkce.) Zelený nadpis označuje, že žádný test nehavaroval. (V tom případě dílčí výsledky nejsou uvedeny.)',
'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'Jestliže vaše aplikace používá jinou databázi než SQLite, budete muset na PythonAnywhere konfigurovat její DAL() připojení.',
'import': 'import',
'Import/Export': 'Import/Export',
'In development, use the default Rocket webserver that is currently supported by this debugger.': 'Při vývoji je doporučeno použít předvolený webserver Rocket, se kterým tento debugger spolupracuje.',
'includes': 'zahrnuje',
'Indent with tabs': 'Odsazení tabelátory',
'Index': 'Index',
'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'inspect attributes',
'inspect attributes': 'prohlédnout atributy',
'Install': 'Instalovat',
'Installation of %(plugin)s for %(app)s': 'Instalace %(plugin)s pro %(app)s',
'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka',
'internal error': 'vnitřní chyba',
'internal error: %s': 'vnitřní chyba: %s',
'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod',
'Invalid action': 'Chybná akce',
'Invalid application name': 'Nesprávné jméno aplikace',
'invalid circular reference': 'nepovolený kruhový odkaz',
'Invalid email': 'Neplatný email',
'Invalid git repository specified.': 'Byl zadán nesprávný git repozitář.',
'Invalid password': 'Nesprávné heslo',
'invalid password': 'nesprávné heslo',
'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek',
'Invalid request': 'Nesprávný požadavek (request)',
'invalid table names (auth_* tables already defined)': 'chybná jména tabulek (auth_* tabulky už byly definovány)',
'invalid ticket': 'chybný tiket',
'Is Active': 'Je aktivní',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč',
'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'Key bindings': 'Vazby kláves',
'Key bindings for ZenCoding Plugin': 'Vazby kláves pro ZenCoding Plugin',
'Keyboard shortcuts': 'Klávesové zkratky',
'kill process': 'likvidovat proces',
'language file "%(filename)s" created/updated': 'jazykový soubor "%(filename)s" byl vytvořen/aktualizován',
'Language files (static strings) updated': 'Jazykové soubory (statické řetězce) byly aktualizovány',
'languages': 'jazyky',
'Languages': 'Jazyky',
'Last name': 'Příjmení',
'Last Revision': 'Minulá verze',
'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro',
'License:': 'Licence:',
'Line Nr': 'Č.řádku',
'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku',
'Live Chat': 'Online pokec',
'lists by exception': 'výpis podle výjimky',
'lists by ticket': 'výpis podle tiketu',
'Live Chat': 'Online chat',
'Loading...': 'Nahrávám...',
'loading...': 'nahrávám...',
'locals': 'locals',
'Local Apps': 'Lokální aplikace',
'locals': 'lokální proměnné',
'Locals##debug': 'Lokální proměnné',
'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně',
'Login': 'Přihlásit se',
'login': 'přihlásit se',
'Login': 'Přihlásit se',
'Login successful': 'Přihlášení bylo úspěšné',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'Login/Register': 'Přihlásit se / Registrovat',
'logout': 'odhlásit se',
'Logout': 'Odhlásit se',
'lost password': 'ztracené heslo',
'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?',
'Manage': 'Manage',
'Manage Cache': 'Manage Cache',
'Main Menu': 'Hlavní nabídka',
'Manage': 'Spravovat',
'Manage %(action)s': 'Spravovat %(action)s',
'Manage Access Control': 'Spravovat řízení přístupu',
'Manage Admin Users/Students': 'Spravovat Admin uživatele / Studenty',
'Manage Cache': 'Spravovat cache',
'Manage Students': 'Spravovat studenty',
'Memberships': 'Členství ve skupinách',
'Menu Model': 'Model rozbalovací nabídky',
'merge': 'sloučit',
'Models': 'Modely',
'models': 'modely',
'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy',
'Modules': 'Moduly',
'modules': 'moduly',
'Multi User Mode': 'Víceuživatelský mód',
'My Sites': 'Správa aplikací',
'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena',
'new application "%s" imported': 'nová aplikace "%s" byla importována',
'New Application Wizard': 'Nový průvodce aplikací',
'New application wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo',
'new plugin installed': 'nový plugin byl instalován',
'New plugin installed: %s': 'Nový plugin byl instalován: %s',
'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit primitivní aplikaci',
'next': 'next',
'New simple application': 'Vytvořit novou aplikaci',
'next': 'další',
'next %s rows': 'dalších %s řádků',
'next 100 rows': 'dalších 100 řádků',
'NO': 'NE',
'no changes': 'beze změn',
'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala',
'no match': 'nenalezena shoda',
'no package selected': 'nebyla vybrána žádná package',
'no permission to uninstall "%s"': 'chybí oprávnění odinstalovat "%s"',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Node:': 'Uzel (node):',
'Not Authorized': 'Chybí autorizace',
'Not supported': 'Není podporováno',
'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.': 'Poznámka: Dostanete-li chybu s github status code = 128, ujistěte se, že systém a účet z něhož provádíte nasazení má odpovídající ssh klíč, konfigurovaný v OpenShift účtu.',
'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo',
"On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": 'Pro použití tohoto debuggeru na produkci je potřeba konfigurovat webserver, aby používal jeden proces a více vláken.',
'online designer': 'online návrhář',
'Online examples': 'Příklady online',
'Open new app in new window': 'Open new app in new window',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Or Get from URL:',
'Open new app in new window': 'Otevřít novou aplikaci v novém okně',
'OpenShift Deployment Interface': 'OpenShift rozhraní pro nasazení aplikace',
'OpenShift Output': 'OpenShift výstup',
'or alternatively': 'nebo případně',
'Or Get from URL:': 'Nebo získat z URL adresy:',
'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad',
@@ -293,30 +438,53 @@
'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované',
'pack plugin': 'pack plugin',
'password': 'heslo',
'Pack custom': 'Zabalit volitelně (custom)',
'pack plugin': 'zabalit plugin',
'Password': 'Heslo',
'password': 'heslo',
'password changed': 'heslo bylo změněno',
"Password fields don't match": 'Hesla se neshodují',
'Peeking at file': 'Peeking at file',
'Past revisions': 'Minulá verze',
'Path to appcfg.py': 'Cesta ke appcfg.py',
'Path to local openshift repo root.': 'Cesta ke kořenu (rootu) lokálního OpenShift repozitáře.',
'Peeking at file': 'Sledování souboru',
'Permission': 'Oprávnění',
'Permissions': 'Oprávnění',
'Please': 'Prosím',
'Plugin "%s" in application': 'Plugin "%s" in application',
'Please wait, giving pythonanywhere a moment...': 'Prosím, čekejte na dokončení činnosti PythonAnywhere...',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" byl odstraněn',
'Plugin "%s" in application': 'Plugin "%s" v aplikaci',
'plugin not specified': 'plugin nebyl určen',
'Plugin page': 'Stránka pluginů',
'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Plural Form #%s',
'Plural Form #%s': 'Množné číslo #%s',
'Plural-Forms:': 'Množná čísla:',
'Powered by': 'Poháněno',
'Powered by': 'používá technologii',
'Preface': 'Předmluva',
'Preferences saved correctly': 'Nastavení byla úspěšně uložena',
'Preferences saved on session only': 'Nastavení byla uložena pouze pro toto sezení',
'previous %s rows': 'předchozích %s řádků',
'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory',
'profile': 'profil',
'Project Progress': 'Vývoj projektu',
'Pull': 'Pull',
'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull selhal, některé soubory nelze zkopírovat. Pro podrobnosti zkontrolujte logy.',
'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull nelze provést, protože máte nesloučené soubory. Vyřešte tyto konflikty a pak akci opakujte.',
'Push': 'Push',
'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push selhal, protože máte nesloučené soubory. Vyřešte tyto konflikty a pak akci opakujte.',
'pygraphviz library not found': 'pygraphviz knihovna nebyla nalezena',
'Python': 'Python',
'PythonAnywhere Apps': 'PythonAnywhere aplikace',
'PythonAnywhere Password': 'PythonAnywhere heslo',
'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady',
'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána',
'Rapid Search': 'Rychlé hledání',
'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to',
'Record': 'Záznam',
@@ -335,114 +503,167 @@
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše',
'request': 'request',
'Repository (%s)': 'Repozitář (%s)',
'request': 'požadavek (request)',
'requires distutils, but not installed': 'vyžaduje distutils, jenže ty nejsou instalovány',
'requires python-git, but not installed': 'vyžaduje python-git, který ale není nainstalován',
'Reset Password key': 'Reset registračního klíče',
'response': 'response',
'Resolve Conflict file': 'Vyřešit konflikty',
'response': 'odpověď (response)',
'restart': 'restart',
'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return',
'Revert': 'Vrátit se k původnímu',
'revert': 'vrátit se k původnímu',
'reverted to revision %s': 'vráceno k verzi %s',
'Revision %s': 'Verze %s',
'Revision:': 'Verze:',
'Role': 'Role',
'Roles': 'Role',
'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována',
'Run tests': 'Spustit testy',
'Run tests in this file': 'Spustit testy v souboru',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s',
'Save': 'Uložit',
'Save file:': 'Save file:',
'Save file:': 'Uložit soubor:',
'Save file: %s': 'Uložit soubor: %s',
'Save model as...': 'Uložit model jako...',
'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:',
'Screenshot %s': 'Screenshot %s',
'Search': 'Hledání',
'Select Files to Package': 'Vybrat soubory pro package',
'Semantic': 'Modul semantic',
'Services': 'Služby',
'session': 'session',
'session expired': 'session expired',
'session': 'session (sezení)',
'session expired': 'vypršela session',
'Session saved correctly': 'Session byla úspěšně uložena',
'Session saved on session only': 'Session byla uložena jen pro toto sezení',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka',
'Singular Form': 'Singular Form',
'Showing %s to %s of %s %s found': 'Zobrazuji %s%s z %s %s nalezených',
'Singular Form': 'Jednotné číslo',
'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:',
'skip to generate': 'skip to generate',
'skip to generate': 'přeskočit pro vytvoření',
'some files could not be removed': 'některé soubory nelze odstranit',
'Something went wrong please wait a few minutes before retrying': 'Něco se nepodařilo. Vyčkejte několik minut a pak zkuste znova',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'source : db': 'zdroj : db',
'source : filesystem': 'zdroj : souborový systém',
'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce',
'state': 'stav',
'Static': 'Static',
'Static': 'Statické soubory',
'static': 'statické soubory',
'Static files': 'Statické soubory',
'Statistics': 'Statistika',
'Step': 'Step',
'step': 'step',
'stop': 'stop',
'Step': 'Krok',
'step': 'krok',
'stop': 'zastavit',
'Stylesheet': 'CSS styly',
'submit': 'odeslat',
'Submit': 'Odeslat',
'successful': 'úspěšně',
'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'switch to : db': 'přepnout na : db',
'switch to : filesystem': 'přepnout na : souborový systém',
'Tab width (# characters)': 'Šířka tabelátoru (# znaků)',
'Table': 'tabulka',
'Table name': 'Název tabulky',
'Temporary': 'Dočasný',
'test': 'test',
'Testing application': 'Testing application',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'Testing application': 'Zkušební aplikace',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je například "db.tabulka1.pole1==\'hodnota\'". Dotaz se dvěma tabulkami "db.tabulka1.pole1==db.tabulka2.pole2" vytvoří SQL JOIN.',
'The app exists, was created by wizard, continue to overwrite!': 'Aplikace existuje, byla vytvořena průvodcem. Pokračováním ji přepíšete !',
'The app exists, was NOT created by wizard, continue to overwrite!': 'Aplikace existuje, a NEBYLA vytvořena průvodcem. Pokračováním ji přepíšete !',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je slovník, který se zobrazil v pohledu %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The data representation, define database tables and sets': 'Modely se vykonají při každém přístupu. Zde se obvykle definuje především reprezentace dat: Připojení k databázi a struktura tabulek databáze',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je dictionary (slovník), který se zobrazil pomocí šablony (view) %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: šablony (neboli pohledy, templaty, view). Mixuje Html, Python kód a Python data.',
'The Views': 'Pohledy (The Views)',
'There are no controllers': 'There are no controllers',
'There are no modules': 'There are no modules',
'Theme': 'Téma',
'There are no controllers': 'Nejsou vytvořeny žádné controllery',
'There are no models': 'Není vytvořen žádný model',
'There are no modules': 'Nejsou přidány žádné moduly',
'There are no plugins': 'Žádné moduly nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'There are no static files',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no views': 'There are no views',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'There are no static files': 'Nejsou přidány žádné statické soubory',
'There are no translators': 'Není vytvořen žádný překlad',
'There are no translators, only default language is supported': 'Není vytvořen žádný překlad, je podporován jen defaultní jazyk',
'There are no views': 'Nejsou vytvořeny žádné šablony (views)',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou přístupné jen běžící aplikaci. Nejsou dostupné uživatelům, ani se nekopírují do případného vývojového repozitáře. Hesla a citlivá nastavení nedávejte nikam jinam.',
'These files are served without processing, your images go here': 'Tyto soubory jsou stahovány přímo, bez jakékoli přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is the %(filename)s template': 'This is the %(filename)s template',
"This debugger may not work properly if you don't have a threaded webserver or you're using multiple daemon processes.": 'Tento debugger nebude pracovat správně, jestliže váš webový server nepracuje pomocí vláken nebo když používáte více procesů démonů.',
'This is a copy of the scaffolding application': 'Toto je kopie vzorové aplikace.',
'This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk': 'Toto je experimentální vlastnost, která vyžaduje další testování. Návrat verze jen na vlastní riziko.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'Toto je experimentální vlastnost, která vyžaduje další testování. Upgrade verze jen na vlastní riziko.',
'This is the %(filename)s template': 'Toto je šablona %(filename)s',
"This page can commit your changes to an openshift app repo and push them to your cloud instance. This assumes that you've already created the application instance using the web2py skeleton and have that repo somewhere on a filesystem that this web2py instance can access. This functionality requires GitPython installed and on the python path of the runtime that web2py is operating in.": 'Tato stránka umožňuje potvrdit vaše změny do OpenShift aplikačního repozitáře a odeslat je do vaší cloud instance. Předpokladem je, že jste už vytvořili aplikační instanci pomocí Web2py předlohy a že tento repozitář máte někde na disku tak, aby k němu Web2py mělo přístup. Tato funkcionalita vyžaduje, aby byl instalován GitPython a aby mohl být nalezen pomocí cesty, se kterou Web2py pracuje.',
'This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.': 'Tato stránka umožňuje zkopírovat vaši aplikaci do Google App Engine cloudu. Pamatujte, že nejprve je třeba vytvořit indexy lokálně, čehož dosáhnete instalací Google appserver a jedním lokálním spuštěním aplikace. V opačném případě bude docházet k chybám vyhledávání. Pozor: v závislosti na rychlosti sítě může nasazení trvat dlouhou dobu. Pozor: bude přepsán váš soubor app.yaml. BĚHEM SPUŠTĚNÍ NESPOUŠTĚJTE PODRUHÉ.',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'This will pull changes from the remote repo for application "%s"?': 'Toto stáhne (pull) změny ze vzdáleného repozitáře pro aplikaci "%s"?',
'This will push changes to the remote repo for application "%s".': 'Toto nahraje (push) změny do vzdáleného repozitáře pro aplikaci "%s".',
'Ticket': 'Tiket',
'Ticket ID': 'ID tiketu',
'Ticket Missing': 'Chybový tiket chybí',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete skupinu souborů nebo adresář(e) plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle comment': 'Přepnout komentář',
'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké',
'Traceback': 'Traceback',
'Traceback': 'Hierarchie volání',
'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'try something like',
'try something like': 'zkuste něco jako',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'try view',
'try view': 'vyzkoušet šablonu (view)',
'Twitter': 'Twitter',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'Unable to check for upgrades',
'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Zapište příkaz PDB debuggeru a stiskněte Return (Enter) pro jeho provedení.',
'Type python statement in here and hit Return (Enter) to execute it.': 'Zapište příkaz pythonu a stiskněte Return (Enter) pro jeho provedení.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Zapište kód v jazyce python a stiskněte Return (Enter) pro jeho provedení.',
'Unable to check for upgrades': 'Nelze zjistit informaci o aktualizacích',
'unable to create application "%s"': 'nelze vytvořit aplikaci "%s"',
'unable to delete file "%(filename)s"': 'nelze zrušit soubor "%(filename)s"',
'unable to delete file plugin "%(plugin)s"': 'nelze zrušit plugin "%(plugin)s"',
'Unable to determine the line number!': 'Nelze určit číslo řádky!',
'Unable to download app because:': 'Nelze stáhnout aplikaci, protože:',
'unable to download layout': 'nelze stáhnout šablonu (layout)',
'unable to download plugin: %s': 'nelze stáhnout plugin: %s',
'Unable to download the list of plugins': 'Nelze stáhnout seznam pluginů',
'unable to install plugin "%s"': 'nelze instalovat plugin "%s"',
'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'unable to uninstall "%s"': 'nelze instalovat "%s"',
'unable to upgrade because "%s"': 'nelze upgradovat, protože "%s"',
'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat',
'Unsupported webserver working mode: %s': 'Nepodporovaný mód webového serveru: %s',
'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky',
'update all languages': 'aktualizovat všechny jazyky o nové texty ze zdrojových souborů',
'Update:': 'Upravit:',
'Upgrade': 'Upgrade',
'upgrade now': 'upgrade now',
'upgrade now to %s': 'upgrade now to %s',
'upgrade now': 'upgradovat nyní',
'upgrade now to %s': 'upgradovat nyní na %s',
'upload': 'nahrát',
'Upload': 'Upload',
'Upload': 'Upload (nahrát)',
'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User': 'Uživatel',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
@@ -451,30 +672,45 @@
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno',
'variables': 'variables',
'Users': 'Uživatelé',
'Using the shell may lock the database to other users of this app.': 'Použití příkazového shellu může uzamknout databázi ostatním uživatelům této aplikace.',
'variables': 'proměnné',
'Verify Password': 'Zopakujte heslo',
'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování',
'Videos': 'Videa',
'View': 'Pohled (View)',
'Views': 'Pohledy',
'views': 'pohledy',
'Web Framework': 'Web Framework',
'View': 'Šablona (View)',
'Views': 'Šablony (Views)',
'views': 'šablony (views)',
'Warning!': 'Pozor!',
'WARNING:': 'POZOR:',
'WARNING: The following views could not be compiled:': 'POZOR: Následující šablony se nepodařilo zkompilovat:',
'Web Framework': 'Webový framework',
'web2py Admin Password': 'web2py Heslo administrátora',
'web2py apps to deploy': 'web2py aplikace k nasazení',
'web2py Debugger': 'web2py Debugger',
'web2py downgrade': 'web2py downgrade',
'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'web2py upgrade',
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'web2py Recent Tweets': 'Nedávné tweety na Twitteru o web2py',
'web2py upgrade': 'aktualizace Web2py',
'web2py upgraded; please restart it': 'Web2py bylo aktualizováno; prosím restarujte jej',
'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve web2py',
'Welcome to web2py!': 'Vítejte ve web2py!',
'Welcome to web2py': 'Vitejte ve Web2py aplikaci.',
'Welcome to web2py!': 'Vítejte ve Web2py aplikaci.',
'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.',
'WSGI reference name': 'jméno WSGI reference',
'YES': 'ANO',
'Yes': 'Ano',
'You are successfully running web2py': 'Úspěšně jste spustili web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'You can inspect variables using the console below': 'You can inspect variables using the console below',
'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.',
'You have one more login attempt before you are locked out': 'Máte jen jeden další pokus k přihlášení před zablokováním',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You only need these if you have already registered': 'Toto potřebujete jen tehdy, jestliže jste se už registroval(a)',
'You visited the url %s': 'Navštívili jste stránku %s,',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
}
+14 -6
View File
@@ -4,6 +4,7 @@ import time
from gluon import portalocker
from gluon.admin import apath
from gluon.fileutils import read_file
from gluon.utils import web2py_uuid
# ###########################################################
# ## make sure administrator is on localhost or https
# ###########################################################
@@ -49,15 +50,18 @@ except IOError:
def verify_password(password):
session.pam_user = None
if DEMO_MODE:
return True
ret = True
elif not _config.get('password'):
return False
ret - False
elif _config['password'].startswith('pam_user:'):
session.pam_user = _config['password'][9:].strip()
import gluon.contrib.pam
return gluon.contrib.pam.authenticate(session.pam_user, password)
ret = gluon.contrib.pam.authenticate(session.pam_user, password)
else:
return _config['password'] == CRYPT()(password)[0]
ret = _config['password'] == CRYPT()(password)[0]
if ret:
session.hmac_key = web2py_uuid()
return ret
# ###########################################################
@@ -100,13 +104,12 @@ def write_hosts_deny(denied_hosts):
portalocker.unlock(f)
f.close()
def login_record(success=True):
denied_hosts = read_hosts_deny()
val = (0, 0)
if success and request.client in denied_hosts:
del denied_hosts[request.client]
elif not success and not request.is_local:
elif not success:
val = denied_hosts.get(request.client, (0, 0))
if time.time() - val[1] < expiration_failed_logins \
and val[0] >= allowed_number_of_attempts:
@@ -117,6 +120,11 @@ def login_record(success=True):
write_hosts_deny(denied_hosts)
return val[0]
def failed_login_count():
denied_hosts = read_hosts_deny()
val = denied_hosts.get(request.client, (0, 0))
return val[0]
# ###########################################################
# ## session expiration
+10 -5
View File
@@ -12,24 +12,29 @@ def button(href, label):
if is_mobile:
ret = A_button(SPAN(label), _href=href)
else:
ret = A(SPAN(label), _class='btn rounded', _href=href)
ret = A(SPAN(label), _class='button btn', _href=href)
return ret
def button_enable(href, app):
if os.path.exists(os.path.join(apath(app, r=request), 'DISABLED')):
text, classes = T("Enable"), "btn rounded red"
label = SPAN(T('Enable'), _style='color:red')
else:
text, classes = T("Disable"), "btn rounded gree"
label = SPAN(T('Disable'), _style='color:green')
id = 'enable_' + app
return A(text, _class=classes, _id=id, callback=href, target=id)
return A(label, _class='button btn', _id=id, callback=href, target=id)
def sp_button(href, label):
if request.user_agent().get('is_mobile'):
ret = A_button(SPAN(label), _href=href)
else:
ret = A(SPAN(label), _class='btn pink rounded', _href=href)
ret = A(SPAN(label), _class='button special btn btn-inverse', _href=href)
return ret
def helpicon():
return IMG(_src=URL('static', 'images/help.png'), _alt='help')
def searchbox(elementid):
return SPAN(LABEL(IMG(_id="search_start", _src=URL('static', 'images/search.png'), _alt=T('filter')),
_class='icon', _for=elementid), ' ',
INPUT(_id=elementid, _type='text', _size=12, _class="input-medium"),
_class="searchbox")
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+489
View File
@@ -0,0 +1,489 @@
/*=============================================================
GENERAL
==============================================================*/
body { /*remember to account for the hidden area underneath
fixed navbar by adding at least 40px padding to the <body>.
Be sure to add this after the core Bootstrap CSS
and before the optional responsive CSS.
An alternative solution is to set top-margin to div#main padding-top:60px; comment this for alternative solution*/ height:auto; /*uncomment this for alternative solution*/ }
/*=============================================================
BOOTSTRAP ICONS FOLDER FIX
==============================================================*/
[class^="icon-"], [class*=" icon-"] { /* right folder for bootstrap black images/icons*/ background-image:url("../images/glyphicons-halflings.png") }
.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-"] { /* right folder for bootstrap white images/icons*/ background-image:url("../images/glyphicons-halflings-white.png"); }
/*=============================================================
INPUT BORDER HIGHLIGHT WHEN INPUT IS FOCUSED
==============================================================*/
textarea:focus, input[type="text"]:focus, input[type="password"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, input[type="file"]:focus, select:focus, .uneditable-input:focus { /* outline color*/ border-color:rgba(232, 149, 60, 0.8); outline:0; /*outline:thin dotted \9;*/ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); }
.web2py_grid .dropdown-menu li > a:hover, .web2py_grid .dropdown-menu li > a:focus { filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9*/ background-image:none; background-color:#E8953C; }
/*=============================================================
COLOR OF LINKS
==============================================================*/
a, a:hover { color:#E8953C; text-decoration:none; }
a:hover { color:#e2821b; }
/*=============================================================
CONTROLS and CONTAINERS
==============================================================*/
.row-buttons .btn { margin-bottom:7px; }
.sidebar .box { clear:right; margin-top:2em; border-top:1px solid #d1d1d1; padding:0 1em; }
.pwdchange>.button { margin-bottom:10px; }
input[type="file"] { margin-bottom:9px; }
.form-inline input[type="file"] { margin-bottom:0px; }
input + .help-block { margin-top:-10px; margin-bottom:4px; }
#confirm_form input.btn, .generatedbyw2p input { margin-right:4px; }
a[rel='tooltip'] span, div[rel='tooltip'] span { display:none; margin-left:-9999px; }
/*in-page browsing*/
[rel="pagebookmark"] { position:relative; }
[rel="pagebookmark"]>.component { cursor:pointer; }
[rel="pagebookmark"]>.hashstick { position:absolute; top:-54px; left:-9999px; visibility:visible; }
/* following 2 rules set the style of a small button for going to top of page*/
.tophashlink.btn { padding:2px 3px; visibility:hidden; }
.hashstick:target+.tophashlink.btn { visibility:visible; }
ul.act_edit { margin-top:4px; margin-left:20px; }
ul.act_edit .btn { margin-top:4px; margin-bottom:4px; }
ul.act_edit .file>a { white-space:pre; }
.right-full { text-align:right; }
.searchbox, .searchbox label, .searchbox input { display:inline-block; }
.buttons-row .btn { margin-bottom:9px; }
.li-controls { display:inline-block; width:180px; vertical-align:middle; }
.celled { display:inline-block; padding: 0 0 0 4px; vertical-align:top; margin-top:4px; width:700px; }
.folder { list-style-type:none; #border-left: 1px dotted #AAA; }
.folder li { list-style-type:none; }
.folder>i { display:inline-block; width:5px; height:5px; border:1px solid; background-color:#FAA732; margin-left:-4px; margin-top:-2px; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border-radius:1px; }
.folder>i+a { padding-left:0.5em; }
.folder ul { margin-top:0.5em; margin-bottom:0.5em; }
.controls-inline .btn { margin-right:5px; }
div.web2py_counter.span6 { min-height:24px; text-align:right; }
.pagination { margin:0; }
.table { margin-bottom:10px; }
.row_buttons .btn { margin-right:4px; }
.editor-bar-column { display:inline-block; vertical-align:top; margin-right:4px; }
.editor-bar-column .input-long { width:270px; }
.editor-bar-column .input-normal { width:206px; }
.keybindings li { margin-bottom:0.5em; }
.keybindings span { padding:0.3em; border:1px solid transparent; vertical-align:middle; }
.teletype-text { font-family:monospace; font-weight:bold; font-style:normal; border-color:#999; background:#333; color:#DDD; -moz-border-radius:0.3em; border-radius:0.3em; }
.edit_language .tab_row div { display:inline-block; vertical-align:top; margin-right:4px; }
.edit_language .fake-input { height:18px; padding:4px; font-size:13px; line-height:18px; overflow:hidden; white-space:nowrap; display:inline-block; margin-bottom:9px; }
.test h3 { padding-left:9px; margin:0; font-size:16px; line-height:1; border-left:9px solid transparent; }
.test h3.passed { border-color:#009900; }
.test h3.failed { border-color:#CC0000; }
.test h3.nodoctests { border-color:#CCCC99; }
.test .test_report { width:100%; overflow:auto; }
.test_report pre { white-space:pre; }
.test div[id^="output_"]>h2 { font-size:18px; line-height:1; color:grey; }
div.center { text-align:center; }
.delete h2 { word-wrap:break-word; }
/*=============================================================
SHELL
==============================================================*/
.shell .output-wrapper { width:100%; height:30em; border:1px solid #333; }
.shell .prompt-wrapper { float:left; width:100%; overflow:hidden; height:auto; border:1px solid #333; }
.shell .prompt-container { margin-left:2.5em; }
.shell #caret { width:2.5em; float:left; margin-left:-100%; }
.shell #shellwrapper { background:white; color:#E8953C; width:100%; margin:1em 0; border:0; }
.shell #output, .shell .prompt { color:#E8953C; background:white; resize:none; border:none; width:100%; height:100%; cursor:default; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
.shell #output:focus, .shell .prompt:focus { border-color:transparent; outline:0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.shell #output pre { color: #E8953C; }
.shell #autoscroll { cursor:pointer; float:right; }
.shell .prompt, .shell #output, .shell #caret { font-size: 11pt; padding: 6px; padding-right: 0em; }
.shell #caret { padding-top:9px; }
.shell .prompt, .shell #output, .shell pre, .shell #caret { font-family: monospace; }
.shell a[rel="tooltip"] { margin-left:8px; }
/*=============================================================
PEEK
==============================================================*/
.peek .code-wrapper { width:100%; overflow:auto; white-space:pre; }
.peek table td pre { word-break:normal; white-space:pre; }
/*=============================================================
FOOTER
==============================================================*/
#footer { border-top:1px solid; text-align:center; padding:1em 0; }
#footer span, #footer select { display:inline-block; margin-bottom:0; vertical-align:middle; }
#footer select { width:auto; }
/*=============================================================
MAIN
==============================================================*/
#main { margin-top:60px; /*uncomment this for alternative solution to hidden area underneath fixed navbar issue*/ margin-bottom:60px; }
/*=============================================================
WIZARD
==============================================================*/
#wizard_nav .box { border-bottom:1px dotted; }
#wizard_nav li { margin-left:1em; margin-top:0.5em; }
.step textarea { width:auto; }
select[name='layout_theme'] { vertical-align:top; }
img#preview { margin-bottom:9px; }
/* multiselect customization*/
.ms-container { margin-bottom:5px; }
.ms-selectable, .step .ms-selection { text-align:center; }
.ms-list { text-align:left; background:white; }
.ms-container li.ms-elem-selectable:not(.disabled).ms-hover, .ms-container .ms-selection li:not(.disabled).ms-hover { background-color:#E8953C; }
.ms-container .ms-selectable { margin-right:25px; }
.ms-container .ms-selectable, .ms-container .ms-selection { background:transparent; }
.ms-container .ms-list.ms-focus { border-color:rgba(232, 149, 60, 0.8); -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(232, 149, 60, 0.6); }
/* grow_input*/
ul[id$="_grow_input"] { margin-left:0; }
/* generate_form*/
#generate_form .control-group { margin-bottom:0; }
#generate_form .control-label { text-align:left; }
#generate_form .controls { padding-left:18px; margin-left:0; }
#generate_form .control-label.empty { width:142px; }
.step [rel="pagebookmark"]>.hashstick { display:none; }
/*generated page*/
.generated iframe { border:1px inset #e3e3e3; }
/*=============================================================
ERRORS TABLE / TICKET PAGE
==============================================================*/
.tablebar { margin:7px 0 7px 0; }
.tablebar input { margin-right:27px; }
.tablebar span { vertical-align:bottom; }
.table th { background: #e9e9e9; background: -moz-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FAFAFA), color-stop(100%, #E9E9E9)); background: -webkit-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -o-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -ms-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9')"; /*font-size:10px; color:#444; text-transform:uppercase;*/ }
td.cbcentered, th.cbcentered { text-align:center; }
td.cbcentered>input, th.cbcentered>input { margin-top:-1px; }
.traceback div { }
.ticket_code>table td:first-child { border-left:0; }
#trck_errors table td pre { word-break:normal; white-space:pre; }
.inspect pre, .errorsource pre { word-break:normal; white-space:pre; }
.ticket_code { background-color:lightyellow; }
.ticket_code table, .ticket_code td { border-width:0px; border-collapse:collapse; width:100%; }
.ticket_code tbody tr:hover td { background-color:transparent; }
/*=============================================================
FLOT GRAPHS
==============================================================*/
.about #placeholder { width:auto; max-width:600px; height:300px; position:relative; margin:0 auto; /* for centering*/ }
/*=============================================================
THE GRID
==============================================================*/
#w2p_query_panel { min-width:20px; min-height:20px; padding:10px; margin-top:1em; background-color:#f5f5f5; border: 1px solid #e3e3e3; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); }
#w2p_query_panel select, #w2p_query_panel input { margin-bottom:0; margin-right:4px; }
.web2py_grid .hidden { visibility:visible; }
.qry_pnl_btns { display:inline-block; }
#w2p_grid_addbtn, #w2p_search-form { margin-top:9px; margin-bottom:9px; }
#w2p_search-form { margin-bottom:0; }
#w2p_search-form form { margin-bottom:0; }
/*----- translate page ---*/
.languageform input { margin-bottom:0; }
.languageform input.untranslated { background-color:#FC0; }
/*=============================================================
MASKED UPLOAD INPUT (NO BOOTSTRAP RELATED)
==============================================================*/
#appupdate_file.masked {
margin: 0;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /* IE 8 */
filter: alpha(opacity=0); /* IE 7 */
font-size: 100px;
position: absolute;
top: 0;
right: 0;
z-index: 410;
}
#fileselect {
padding: 4px 6px;
border: 1px solid #ccc;
border-radius: 4px;
color: #555;
cursor: default;
position: relative;
z-index: 400;
font-size: 14px;
background-color: #fff;
margin-bottom: 10px;
overflow: hidden;
}
#fileselect span {
position: absolute;
left: 6px;
top: 4px;
}
.uploadbtn {
position: absolute;
top: 3px;
right: 3px;
}
.txtPlaceholder {
font-style: italic;
color: #ccc;
}
/*=============================================================
EDIT PAGE SLIDING FILES MENU
==============================================================*/
@media (max-width: 979px) {
body.edit div#header {position:relative; z-index: 1030 !important;}
}
#editor_area, #edit_placeholder {
margin: 0;
padding: 0;
}
#editor_area {
position: relative;
box-sizing: border-box;
}
#files {
width: auto;
height: 100%;
margin: 0;
padding: 0;
position: fixed;
top: 0px;
left: 0px;
z-index: 1031;
border-right: 3px solid #000;
/* animation (it doesn't work in IE<10) */
-moz-transition: all 0.4s;
-webkit-transition: all 0.4s;
-o-transition: all 0.4s;
transition: all 0.4s;
}
#files:hover, #files:focus {
left: 0px !important;
}
#files, .files-toggle {
background: #1b1b1b;
opacity: 0.98;
}
.files-toggle {
width: 18px;
height: 86px;
border-radius: 0px 4px 4px 0px;
color: #999;
position: absolute;
top: 60px;
right: -18px;
cursor: default;
}
.arrow {
display: block;
position: absolute;
top: 8px;
width: 18px;
height: 70px;
background: url(../images/files_toggle.png) no-repeat;
}
.files-menu {
height: 100%;
overflow: auto;
}
#filelist {
position: relative;
top: 60px;
padding-bottom: 60px;
}
#filelist li {
padding-right: 8px;
width: 100%;
}
#filelist li>a {
text-shadow: none;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media (max-width: 800px) { .step [rel="pagebookmark"]>.hashstick { /*top:-54px;*/ display:block; }
}
@media (max-width: 767px) { [rel="pagebookmark"]>.hashstick { top:0; }
/*-----------------------------------
main
-------------------------------------*/
#main { margin-top:0; }
/*-----------------------------------
footer
-------------------------------------*/
#footer { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
/*-----------------------------------
errors page
-------------------------------------*/
#trck_errors { table-layout:fixed; }
#trck_errors .column1 { width:20px; }
#trck_errors .column2 { width:45px; }
#trck_errors .column3 { width:150px; }
#trck_errors .columnN { width:55px; }
#trck_errors .columnN1 { width:138px; }
.ticket_code, .inspect.resp1, .inspect.controls pre, .errorsource { width:100%; overflow:auto; }
.ticket_code>table { width:100%; }
.celled { width:320px; }
}
@media (max-width: 480px) { .qry_pnl_btns { display:block; margin-top:4px; }
/*-----------------------------------
wizard
-------------------------------------*/
#generate_form .control-label { float:left; width:160px; padding-top:5px; }
.inspect>code { display:block; white-space:normal; }
.li-controls { }
.celled { width:165px; }
}
/*-----------------------------------
miscellaneous
-------------------------------------*/
h4.editableapp, h4.currentapp { padding: 5px 0 5px 54px; display: inline; }
h4.editableapp { background: #fff url(../images/folder.png) no-repeat; }
h4.currentapp { background: #fff url(../images/folder_locked.png) no-repeat; }
.w2p_flash { position:fixed; width:50%; top:49px; left:25%; right:25%; cursor:default; text-align:center; z-index:5620; }
span#closeflash {position:absolute; top:1px; right:-1px; font-size:150%; border:1px solid black; border-color: transparent transparent #fbeed5 #fbeed5; border-radius: 0 0 0 4px; width:22px; }
span#closeflash:hover {font-weight:bold; cursor:pointer; }
table.twitter{ background-color: transparent; }
table.twitter tr td {vertical-align: top; padding: 5px; }
table.twitter tr { border-bottom: 1px solid #a0a0a0; }
div.error_wrapper {margin-top:-10px; margin-bottom:8px; padding-left:2px; color:#d62e2b; }
.twitter-timeline >iframe{padding: 1em 0;}
+6 -10
View File
@@ -1,11 +1,7 @@
.calendar {z-index:2000;position:relative;margin-top:140px;display:none;background-color:white;border:1px solid #000;color:#000;cursor:default;box-shadow:0 0 10px #666}.calendar * {text-align: center;font-size:10px!important}
.calendar table {border-collapse:collapse}
.calendar tbody tr:hover {background-color:#fbf6d9}
.calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0}
.calendar thead tr {background-color:#f1f1f1}
.calendar tbody tr {border-bottom:2px solid #f1f1f1}
.calendar th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
.calendar thead th {vertical-align:bottom}
.calendar tbody th {vertical-align:top}
.calendar{z-index:99;position:relative;display:none;background:#fff;border:2px solid #000;font-size:11px;color:#000;cursor:default;font-family:Arial,Helvetica,sans-serif;
border-radius: 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
}.calendar table{margin:0px;font-size:11px;color:#000;cursor:default;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;color:#fff;background:#000;}.calendar .nav{background:#000;color:#fff}.calendar thead .title{font-weight:bold;padding:1px;background:#000;color:#fff;text-align:center;}.calendar thead .name{padding:2px;text-align:center;background:#bbb;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite {background-color:#666;}.calendar thead .active{padding:2px 0 0 2px;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;background:#bbb;}.calendar tbody .rowhilite td{background:#ddd;}.calendar tbody td.hilite{background:#bbb;}.calendar tbody td.active{background:#bbb;}.calendar tbody td.selected{font-weight:bold;background:#ddd;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#bbb;padding:1px;background:#000;color:#fff;text-align:center;}.calendar tfoot .hilite{background:#ddd;}.calendar tfoot .active{}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;background:#e4e0d8;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{padding:1px 0;text-align:center;background-color:#bbb;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;font-weight:bold;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}.hour,.minute{font-size:2em;}
#CP_hourcont{z-index:2000;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:2000;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:2000}
#CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99}
-361
View File
@@ -1,361 +0,0 @@
/************
Created by Massimo Di Pierro
Stupid.css is what the names says, take it with a grain of salt
License: BSD
************/
/*** basic styles ***/
* {border:0; margin:0; padding:0; font-familiy:Helvetica}
html, body {max-width: 100vw !important;overflow-x: hidden !important}
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
p, li {margin-bottom:0.5em}
p {text-align:justify}
label, strong {font-weight:bold}
ul {list-style-type:none; padding-left:20px}
a {text-decoration:none; color:#26a69a; white-space:nowrap}
a:hover {cursor:pointer}
h1,h2,h3,h4,h5,h6{font-weight:strong; text-transform:uppercase}
h1{font-size:4em; margin:1.0em 0 0.25em 0}
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
h4{font-size:1.6em; margin:0.7em 0 0.25em 0}
h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9}
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
thead tr {background-color:#f1f1f1}
tbody tr {border-bottom:2px solid #f1f1f1}
th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
thead th {vertical-align:bottom}
tbody th {vertical-align:top}
header, footer {with:100%}
@media (max-width:599px) {
h1{font-size:2em}
h2{font-size:1.8em}
h3{font-size:1.6em}
h4{font-size:1.4em}
h5{font-size:1.2em}
h6{font-size:1.0em}
}
/*** buttons ***/
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; line-height:2.4em; background-color:#26a69a; color:white}
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em; line-height:1.5em}
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
.center {text-align:center !important; margin-left:auto; margin-right:auto}
.center>div {text-align:left}
.right {right:0; text-align:right}
.middle div {vertical-align:middle !important}
.bottom div {vertical-align:bottom !important}
.xscroll {overflow-x:scroll; width:100%}
.yscroll {overflow-y:scroll; width:100%}
.nowrap {white-space:nowrap; overflow-x:hidden}
.fill {width:100%}
.lifted {box-shadow:5px 5px 10px #666}
.relative {position:relative}
.relative>div {position:absolute}
.spaced {margin-bottom:20px; margin-top:20px}
.hidden {display:none}
/*** forms ***/
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outline:none}
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd}
input, textarea, select, button {font-size:12px}
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; vertical-align:top}
.container>.fill {width:100%; margin-right:-20px}
.container img, .container video {max-width:100%}
@media (min-width:800px) {
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
.quarter {width:25%; margin-right:-5px}
.half {width:50%; margin-right:-10px}
.third {width:33.33%; margin-right:-6.66px}
.twothirds {width:66.66%; margin-right:-13.33px}
.threequarters {width:75%; margin-right:-15px}
}
@media (min-width:600px) and (max-width:799px) {
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.threequarters.compressible {width:75%; margin-right:-15px}
.quarter:not(.compressible), .half:not(.compressible), .threequarters:not(.compressible) {width:100%; margin-right:-20px}
.third {width:33.33%; margin-right:-6.66px}
.twothirds {width:66.66%; margin-right:-13.33px}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
}
@media (max-width:599px) {
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.third.compressible {width:33.33%; margin-right:-6.66px}
.twothirds.compressible {width:66.66%; margin-right:-13.33px}
.threequarters.compressible {width:75%; margin-right:-15px}
}
/*** progress bar from http://codepen.io/holdencreative/details/pvxGxy ***/
.progress {
margin-left:-15px;
margin-right:-15px;
position:relative;
height:8px;
display:block;
width:120%;
background-color:#acece6;
border-radius:0 !important;
background-clip:padding-box;
overflow:hidden;
}
.progress .determinate {
position:absolute;
background-color:inherit;
top:0;
bottom:0;
background-color:#26a69a;
transition:width .3s linear;
}
.progress .indeterminate {
background-color:#26a69a;
}
.progress .indeterminate:before {
content:'';
position:absolute;
background-color:inherit;
top:0;
left:0;
bottom:0;
will-change:left, right;
animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.progress .indeterminate:after {
content:'';
position:absolute;
background-color:inherit;
top:0;
left:0;
bottom:0;
will-change:left, right;
animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation-delay:1.15s;
}
@-webkit-keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@-moz-keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@-webkit-keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
@-moz-keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
@keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
/**** dropdown menu from http://codepen.io/philhoyt/pen/ujHzd ***/
.menu {list-style:none; position:relative; margin:0; padding:0}
.menu.right {float:right}
.menu a {padding:0 15px; text-decoration:none;text-align:left;font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; text-align:left}
.menu li {position:relative; float:left; margin:0; padding:0}
.menu ul {background:white; border:1px solid #e1e1e1; visibility:hidden; opacity:0; position:absolute; top:110%; padding:0; z-index:1000; transition:all 0.2s ease-out; list-style-type:none; box-shadow:5px 5px 10px #666}
.menu ul a {padding:10px 15px; color:#333; font-weight:700; font-size:12px; line-height:16px; display: block}
.menu ul li {float:none; width:200px}
.menu ul ul {top:0; left:80%; z-index:2000}
.menu li:hover > ul {visibility:visible; opacity:1}
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
.menu.dark ul {background:black; border:1px solid black}
.menu.dark ul a {color:white}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black}
@media (max-width:599px) {
header .menu li, header .menu ul {width: 100%}
header .menu.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px; z-index:2000}
}
@media (min-width:600px) {
.ham {display:none!important}
.burger.accordion * {max-height:1000px; overflow:visible}
}
/*** pulsating ring from https://jsfiddle.net/mandynicole/7xrKP/ *******/
.pulse:after {
content:"";
border:3px solid #00e6ac;
-webkit-border-radius:30px;
height:40px;
width:40px;
position:absolute;
margin-left:-20px;
margin-top:-20px;
-webkit-animation:pulsate 1s ease-out;
-webkit-animation-iteration-count:infinite;
opacity:0.0
}
@-webkit-keyframes pulsate {
0% {-webkit-transform:scale(0.1, 0.1); opacity:0.0}
50% {opacity:1.0}
100% {-webkit-transform:scale(1.2, 1.2); opacity:0.0}
}
/**** underline effect ***/
a:not(.btn):not(.noeffect) {position:relative}
a:not(.btn):not(.noeffect):hover {color:#26a69a}
a:not(.btn):not(.noeffect):hover:after {width:100%}
a:not(.btn):not(.noeffect):after {
display:block;
position:absolute;
left:0;
bottom:-1px;
width:0;
height:2px;
background-color:#26a69a;
content:"";
transition:width 0.2s;
}
/**** modal ***/
.modal {
position:fixed;
z-index:9999;
top:0;
bottom:0;
left:0;
right:0;
background-color:rgba(0,0,0,0.8);
padding-top:20vh;
transition:opacity 500ms;
visibility:hidden;
opacity:0;
}
.modal:target {visibility:visible; opacity:1}
.modal div {margin-left:auto; margin-right:auto}
.modal .close:not(.btn) {position:absolute; top:10px; right:10px; font-size:20px}
.modal .close {transition:all 200ms}
/*** tooltips from http://codepen.io/trezy/pen/Khnzy ***/
[data-tooltip] {position:relative}
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:hover:before {
border-bottom:.6em solid black;
border-bottom:.6em solid black;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
left:5px;
margin-top:1em;
}
[data-tooltip]:hover:after {
background-color:rgba(0,0,0,0.8) !important;
border:4px solid rgba(0,0,0,0.8) !important;
border-radius:7px !important;
color:white !important;
content:attr(data-tooltip);
text-transform:none;
font-size: 11px;
left:0 !important;
margin-top:1.5em;
padding:5px 15px;
white-space:pre-wrap;
width:100px;
}
/*** accordion ***/
.accordion>input ~ label:before {content:"▲ "; color:#ddd}
.accordion>input:checked ~ label:before {content:"▼ "; color:#ddd}
.accordion>input {display:none}
.accordion>input:checked ~ *:not(label) {
max-height: 1000px !important;
overflow:visible !important;
-webkit-transition: max-height .3s ease-in;
transition: max-height .3s ease-in;
}
.accordion>*:not(label) {
max-height: 0;
overflow: hidden;
margin: 0;
padding: 0;
-webkit-transition: max-height .3s ease-out;
transition: max-height .3s ease-out;
}
/*** cards from http://codepen.io/edeesims/pen/iGDzk ***/
.card {perspective: 500px; max-width:100%}
.card>div {
position: absolute;
width: 100%;
height: 100%;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
transition: transform 1s;
transform-style: preserve-3d;
}
.card:hover>div {
transform: rotateY( 180deg ) ;
transition: transform 0.5s;
}
.card>div>div {
position: absolute;
height: 100%;
width: 100%;
backface-visibility: hidden;
}
.card>div>div:nth-child(2) {
transform: rotateY( 180deg );
}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
/**** tags ****/
.tags > span, .tags > span.off:hover {
height: 30px;
padding: 4px 9px;
text-decoration: none;
margin: 0 5px 30px 0 !important;
white-space: nowrap;
color: white;
background-color: #26a69a;
border-radius: 5px;
line-height: 32px;
}
.tags.dismissible > span:not(.off):hover {
background-color: #ccc !important;
}
.tags.dismissible > span:not(.off):after {
content: " \f00d";
font-family: FontAwesome;
}
.tags > span.off {
background-color: #ccc;
}
-204
View File
@@ -1,204 +0,0 @@
header a {color: white; font-size:1.1em}
main {min-height: 70vh}
.form-group {padding-bottom: 10px !important;}
.w2p_hidden {display:none;visibility:visible}
.right {float:right; text-align:right}
.left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle}
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {white-space: wrap; margin:5px 0}
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; font-size:0.7em; color: black}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em}
/* Fix */
#auth_user_remember__row label {display:inline}
#web2py_user_form td {vertical-align:top}
/*********** web2py specific ***********/
div.w2p_flash {
font-weight:bold;
display:none;
padding:20px 20px 20px 50px;
width:100%;
opacity:0.95;
vertical-align:middle;
cursor:pointer;
color:#000;
background-color:#ffdc00;
z-index:2000;
}
div.w2p_flash:before{content:"×";float:right; margin-right:100px; color:black;}
.ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.w2p_flash:hover { opacity:0.80; }
div.error_wrapper {display:block}
div.error {
color:red;
padding:5px;
display:inline-block;
}
.topbar {
padding:10px 0;
width:100%;
color:#959595;
vertical-align:middle;
padding:auto;
background-image:-khtml-gradient(linear,left top,left bottom,from(#333333),to(#222222));
background-image:-moz-linear-gradient(top,#333333,#222222);
background-image:-ms-linear-gradient(top,#333333,#222222);
background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333333),color-stop(100%,#222222));
background-image:-webkit-linear-gradient(top,#333333,#222222);
background-image:-o-linear-gradient(top,#333333,#222222);
background-image:linear-gradient(top,#333333,#222222);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);
-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
}
.topbar a {
color:#e1e1e1;
}
#navbar {float:right; padding:5px; /* same as superfish */}
.statusbar {
background-color:#F5F5F5;
margin-top:1em;
margin-bottom:1em;
padding:.5em 1em;
border:1px solid #ddd;
border-radius:5px;
-moz-border-radius:5px;
-webkit-border-radius:5px;
}
.breadcrumbs {float:left}
.copyright {float:left}
#poweredBy {float:right}
/* #MEDIA QUERIES SECTION */
/*
*Grid
*
* The default style for SQLFORM.grid even using jquery-iu or another ui framework
* 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_paginator {}
.web2py_grid table {width:100%}
.web2py_grid td {color: black;}
.web2py_console form {
width: 100%;
display: inline;
vertical-align: middle;
margin: 0 0 0 5px;
}
.web2py_console form select {
margin:0;
}
.web2py_search_actions {
float:left;
text-align:left;
}
.web2py_grid .row_buttons {
min-height:25px;
vertical-align:middle;
}
.web2py_grid .row_buttons a {
margin:3px;
}
.web2py_search_actions {
width:100%;
}
.web2py_grid .row_buttons a,
.web2py_paginator ul li a,
.web2py_search_actions a,
.web2py_console input[type=submit],
.web2py_console input[type=button],
.web2py_console button {
line-height:20px;
margin-right:2px; display:inline-block;
padding:3px 5px 3px 5px;
}
.web2py_counter {
margin-top:5px;
margin-right:2px;
width:35%;
float:right;
text-align:right;
}
/*Fix firefox problem*/
.web2py_table {clear:both; display:block}
.web2py_paginator {
padding:5px;
text-align:right;
background-color:#f2f2f2;
}
.web2py_paginator ul {
list-style-type:none;
margin:0px;
padding:0px;
}
.web2py_paginator ul li {
display:inline;
}
.web2py_paginator .current {
font-weight:bold;
}
.web2py_breadcrumbs ul {
list-style:none;
margin-bottom:18px;
}
li.w2p_grid_breadcrumb_elem {
display:inline-block;
}
.web2py_console form { vertical-align: middle; }
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 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.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
.web2py_console .form-control {width: 20%; display: inline;}
.web2py_console #w2p_keywords {width: 50%;}
.web2py_search_actions a, .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { padding: 6px 12px; }
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
// 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 adjust_height_of_collapsed_nav() {
var cn = jQuery('div.collapse');
if (cn.get(0)) {
var cnh = cn.get(0).style.height;
if (cnh>'0px'){
cn.css('height','auto');
}
}
}
function hoverMenu(){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
adjust_height_of_collapsed_nav();
var mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
var 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');});
});
+2 -1
View File
@@ -1,4 +1,5 @@
{{extend 'layout.html'}}
{{block sectionclass}}about{{end}}
<!-- begin "about" block -->
<h2>{{=T("About application")}} "{{=app}}"</h2>
<h3>{{=T("About")}} {{=app}}</h3>
@@ -22,4 +23,4 @@ jQuery(document).ready(function() {
jQuery.plot(jQuery("#placeholder"), [ {{=progress}} ]);
})
</script>
<!-- end "about" block -->
<!-- end "about" block -->
+270 -267
View File
@@ -9,16 +9,19 @@ def peekfile(path,file,vars={},title=None):
return A(file.replace('\\\\','/'),_title=title,_href=URL('peek', args=args, vars=vars))
def editfile(path,file,vars={}):
args=(path,file) if 'app' in vars else (app,path,file)
return A(T('Edit'),_class='btn small rounded black',_href=URL('edit', args=args, vars=vars))
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit', args=args, vars=vars))
def testfile(path,file):
return A(I(_class="fa fa-cog"),**{
'_class':'btn small rounded black',
'_data-tooltip':T("Run tests in this file (to run all files, you may also use the but\
ton labelled 'test')")})
return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')),
SPAN(T("Run tests in this file (to run all files, you may also use the button labelled 'test')"))),
_class='icon test',
_href=URL('test', args=(app, file)),
_rel="tooltip",
**{'_data-placement':'right',
'_data-original-title':T("Run tests in this file (to run all files, you may also use the button labelled 'test')")})
def editlanguagefile(path,file,vars={}):
return A(T('Edit'),_class='button editbutton btn rounded small',_href=URL('edit_language', args=(app, path, file), vars=vars))
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit_language', args=(app, path, file), vars=vars))
def editpluralsfile(path,file,vars={}):
return A(T('Edit'),_class='button editbutton btn rounded small',_href=URL('edit_plurals', args=(app, path, file), vars=vars))
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit_plurals', args=(app, path, file), vars=vars))
def file_upload_form(location, anchor=None):
form=FORM(
LABEL(T("upload file:")),
@@ -56,8 +59,13 @@ def upload_plugin_form(app, anchor=None):
return form
def deletefile(arglist, vars={}):
vars.update({'sender':request.function+'/'+app})
return A(I(_class='fa fa-trash'),_class="red rounded small btn",
_href=URL('delete',args=arglist,vars=vars))
return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')),
SPAN(T('Delete this file (you will be asked to confirm deletion)'))),
_href=URL('delete',args=arglist,vars=vars),
_class='icon delete',
_rel="tooltip",
**{'_data-placement':'right',
'_data-original-title':T('Delete this file (you will be asked to confirm deletion)')})
}}
{{block sectionclass}}design{{end}}
@@ -66,66 +74,64 @@ def deletefile(arglist, vars={}):
<h2>{{=T("Edit application")}} "{{=A(app,_href=URL(app,'default','index'),_target="_blank")}}"</h2>
<!-- COLLAPSE/JUMP-TO BUTTONS -->
<div class="container">
<div class="fill">
<input placeholder="filter" id="search" style="left:100px"/>
</div>
<div class="fill">
<div class="padded">
<a class="btn rounded small black" href="#" onclick="jQuery('.accordion>[type=checkbox]').click();return false">{{=T("collapse/expand all")}}</a>
<a href="#models" class="btn small rounded orange">{{=T("models")}}</a>
<a href="#controllers" class="btn small rounded orange">{{=T("controllers")}}</a>
<a href="#views" class="btn small rounded orange">{{=T("views")}}</a>
<a href="#models" class="btn small rounded orange">{{=T("languages")}}</a>
<a href="#static" class="btn small rounded orange">{{=T("static")}}</a>
<a href="#models" class="btn small rounded orange">{{=T("modules")}}</a>
<a href="#private" class="btn small rounded orange">{{=T("private files")}}</a>
<a href="#plugins" class="btn small rounded orange">{{=T("plugins")}}</a>
</div>
</div>
<div class="right-full controls">
<p class="buttons-row">
{{=searchbox('search')}}
<a class="button special btn btn-inverse" href="#" onclick="jQuery('h3>span').click();return false"><span>{{=T("collapse/expand all")}}</span></a>
<span class="buttongroup">
{{=button('#models', T("models"))}}
{{=button('#controllers', T("controllers"))}}
{{=button('#views', T("views"))}}
{{=button('#languages', T("languages"))}}
{{=button('#static', T("static"))}}
{{=button('#modules', T("modules"))}}
{{=button('#private', T("private files"))}}
{{=button('#plugins', T("plugins"))}}
</span>
</p>
</div>
<!-- MODELS -->
<h5 id="_models">
<label class="component" for="models_inner" data-tooltip="{{=T('The data representation, define database tables and sets')}}">{{=T("Models")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="models_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_models" rel="pagebookmark">
<span class="component" onclick="collapse('models_inner');">{{=T("Models")}}</span>
<a href="#models" rel="tooltip" data-placement="right" data-original-title="{{=T('The data representation, define database tables and sets')}}">
{{=helpicon()}}
<span>{{=T("The data representation, define database tables and sets")}}</span>
</a><span id="models" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="models_inner" class="component_contents">
{{if not models:}}<p><strong>{{=T("There are no models")}}</strong></p>{{else:}}
<div class="controls comptools">
{{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}}
{{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}}
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
{{pass}}
{{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}}
{{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}}
{{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}}
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
{{pass}}
{{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}}
</div>
<ul class="unstyled act_edit">
{{for m in models:}}
{{id="models__"+m.replace('.','__')}}
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls">
{{=editfile('models',m, dict(id=id))}}
{{=deletefile([app, 'models', m], dict(id=id, id2='models'))}}
</span>
<span class="file">
{{=peekfile('models',m, dict(id=id))}}
</span>
<span class="extras">
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}}
</span>
</li>
{{pass}}
{{for m in models:}}
{{id="models__"+m.replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls">
{{=editfile('models',m, dict(id=id))}}
{{=deletefile([app, 'models', m], dict(id=id, id2='models'))}}
</span>
<span class="file">
{{=peekfile('models',m, dict(id=id))}}
</span>
<span class="extras">
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}}
</span>
</li>
{{pass}}
</ul>
{{pass}}
<div class="silver rounded padded">
<button onclick="jQuery('#form1').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
<div class="controls formfield">
<button onclick="jQuery('#form1').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
<div id="form1" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/models/' % app, 'models')}}</div>
</div>
</div>
</div>
</div>
<!-- FIND CONTROLLER FUNCTIONS -->
@@ -135,162 +141,163 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
}}
<!-- CONTROLLERS -->
<h5 id="_controllers">
<label class="component" for="controllers_inner" data-tooltip="{{=T('The application logic, each URL path is mapped in one exposed function in the controller')}}">{{=T("Controllers")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="controllers_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_controllers" rel="pagebookmark">
<span class="component" onclick="collapse('controllers_inner');">{{=T("Controllers")}}</span>
<a href="#controllers" rel="tooltip" data-placement="right" data-original-title="{{=T('The application logic, each URL path is mapped in one exposed function in the controller')}}">
{{=helpicon()}}
<span>{{=T("The application logic, each URL path is mapped in one exposed function in the controller")}}</span>
</a><span id="controllers" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="controllers_inner" class="component_contents">
{{if not controllers:}}<p><strong>{{=T("There are no controllers")}}</strong></p>{{else:}}
<div class="controls comptools">
{{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}}
{{=button(URL('test',args=app), T("test"))}}
{{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}}
{{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}}
{{=button(URL('test',args=app), T("test"))}}
{{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}}
</div>
<ul class="unstyled act_edit">
{{for c in controllers:}}
{{id="controllers__"+c.replace('.','__')}}
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
{{for c in controllers:}}
{{id="controllers__"+c.replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls">
{{=editfile('controllers',c, dict(id=id))}}
{{=deletefile([app, 'controllers', c], dict(id=id, id2='controllers'))}}
{{=testfile('controllers',c)}}
{{=editfile('controllers',c, dict(id=id))}}
{{=deletefile([app, 'controllers', c], dict(id=id, id2='controllers'))}}
{{=testfile('controllers',c)}}
</span>
<span class="file">
{{=peekfile('controllers',c, dict(id=id))}}
{{=peekfile('controllers',c, dict(id=id))}}
</span>
<span class="extras celled">
{{if functions[c]:}}{{=T("exposes")}}{{pass}} {{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
{{if functions[c]:}}{{=T("exposes")}}{{pass}} {{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
</span>
</li>
{{pass}}
</li>
{{pass}}
</ul>
{{pass}}
<div class="silver rounded padded">
<button onclick="jQuery('#form2').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
<div class="controls formfield">
<button onclick="jQuery('#form2').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
<div id="form2" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/controllers/' % app, 'controllers')}}</div>
</div>
</div>
</div>
</div>
<!-- VIEWS -->
<h5 id="_views">
<label class="component" for="views_inner" data-tooltip="{{=T('The presentations layer, views are also known as templates')}}">{{=T("Views")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="views_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
{{if not views:}}<p><strong>{{=T("There are no views")}}</strong></p>{{else:}}
<h3 id="_views" rel="pagebookmark">
<span class="component" onclick="collapse('views_inner');">{{=T("Views")}}</span>
<a href="#views" rel="tooltip" data-placement="right" data-original-title="{{=T('The presentations layer, views are also known as templates')}}">
{{=helpicon()}}
<span>{{=T("The presentations layer, views are also known as templates")}}</span>
</a><span id="views" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="views_inner" class="component_contents">
{{if not views:}}<p><strong>{{=T("There are no views")}}</strong></p>{{else:}}
<div class="controls comptools">
{{=button(LAYOUTS_APP, T("Download layouts from repository"))}}
{{=button(LAYOUTS_APP, T("Download layouts from repository"))}}
</div>
<ul class="unstyled act_edit">
{{for c in views:}}
{{id="views__"+c.replace('/','__').replace('.','__')}}
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls">
{{=editfile('views',c, dict(id=id))}}
{{=deletefile([app, 'views', c], dict(id=id, id2='views'))}}
</span>
<span class="file">
{{=peekfile('views',c, dict(id=id))}}
</span>
<span class="extras celled celled-one">
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
</span>
</li>
{{pass}}
{{for c in views:}}
{{id="views__"+c.replace('/','__').replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls">
{{=editfile('views',c, dict(id=id))}}
{{=deletefile([app, 'views', c], dict(id=id, id2='views'))}}
</span>
<span class="file">
{{=peekfile('views',c, dict(id=id))}}
</span>
<span class="extras celled celled-one">
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
</span>
</li>
{{pass}}
</ul>
{{pass}}
<div class="silver rounded padded">
<button onclick="jQuery('#form3').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
{{pass}}
<div class="controls formfield">
<button onclick="jQuery('#form3').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
<div id="form3" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/views/' % app, 'views')}}</div>
</div>
</div>
</div>
</div>
<!-- LANGUAGES -->
<h5 id="_languages">
<label class="component" for="languages_inner" data-tooltip="{{=T('Translation strings for the application')}}">{{=T("Languages")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="languages_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_languages" rel="pagebookmark">
<span class="component" onclick="collapse('languages_inner');">{{=T("Languages")}}</span>
<a href="#languages" rel="tooltip" data-placement="right" data-original-title="{{=T('Translation strings for the application')}}">
{{=helpicon()}}
<span>{{=T("Translation strings for the application")}}</span>
</a><span id="languages" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="languages_inner" class="component_contents">
{{if not languages:}}<p><strong>{{=T("There are no translators, only default language is supported")}}</strong></p>{{else:}}
<div class="controls comptools">
{{=button(URL('update_languages/'+app), T('update all languages'))}}
{{=button(URL('update_languages/'+app), T('update all languages'))}}
</div>
<ul class="unstyled act_edit">
{{for lang in sorted(languages):
file = lang+'.py'
id = "languages__"+file.replace('.','__')}}
<li id="{{='_'+id}}" class="li-row"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="li-controls">
<span class="filetools controls">
{{=editlanguagefile('languages',file)}}
{{=deletefile([app, 'languages', file], dict(id=id, id2='languages'))}}
</span>
<span class="">
{{=peekfile('languages',file, dict(id=id))}}
</span>
</span> <!-- /li-row -->
<span class="extras celled">
(
{{=T("Plural-Forms:")}}
{{p=languages[lang][3:7]}}
{{if p[2] == 'default':}}
<span class='error text-error'>{{=T("rules are not defined")}}</span> {{=T.M("(file **gluon/contrib/plural_rules/%s.py** is not found)",lang[:2])}}
{{else:}}
{{if p[3] == 1:}}
{{=B(T("are not used"))}}
{{else:}}
{{pfile=p[0]}}
{{if p[1]!=0:}}<span style="display:inline-block;margin-top:-10px;">
<span class="filetools controls">
{{=editpluralsfile('languages',pfile,dict(nplurals=p[3]))}}
</span>
<span class="file">
{{=peekfile('languages',pfile,dict(id=id))}}
</span></span>
{{else:}}
<b>{{=T("are not used yet")}}</b>
{{for lang in sorted(languages):
file = lang+'.py'
id = "languages__"+file.replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark" class="li-row"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="li-controls">
<span class="filetools controls">
{{=editlanguagefile('languages',file)}}
{{=deletefile([app, 'languages', file], dict(id=id, id2='languages'))}}
</span>
<span class="">
{{=peekfile('languages',file, dict(id=id))}}
</span>
</span> <!-- /li-row -->
<span class="extras celled">
(
{{=T("Plural-Forms:")}}
{{p=languages[lang][3:7]}}
{{if p[2] == 'default':}}
<span class='error text-error'>{{=T("rules are not defined")}}</span> {{=T.M("(file **gluon/contrib/plural_rules/%s.py** is not found)",lang[:2])}}
{{else:}}
{{if p[3] == 1:}}
{{=B(T("are not used"))}}
{{else:}}
{{pfile=p[0]}}
{{if p[1]!=0:}}<span style="display:inline-block;margin-top:-10px;">
<span class="filetools controls">
{{=editpluralsfile('languages',pfile,dict(nplurals=p[3]))}}
</span>
<span class="file">
{{=peekfile('languages',pfile,dict(id=id))}}
</span></span>
{{else:}}
<b>{{=T("are not used yet")}}</b>
{{pass}}
{{pass}}
{{pass}}
)
</span>
</li>
{{pass}}
{{pass}}
{{pass}}
)
</span>
</li>
{{pass}}
</ul>
{{pass}}
<div class="silver rounded padded">
<button onclick="jQuery('#form4').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
{{pass}}
<div class="controls formfield">
<button onclick="jQuery('#form4').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button>
<div id="form4" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/languages/' % app, 'languages', T('(something like "it-it")'))}}</div>
</div>
</div>
</div>
</div>
</div>
<!-- STATIC -->
<h5 id="_static">
<label class="component" for="static_inner" data-tooltip="{{=T('These files are served without processing, your images go here')}}">{{=T("Static")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="static_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_static" rel="pagebookmark">
<span class="component" onclick="collapse('static_inner');">{{=T("Static")}}</span>
<a href="#static" rel="tooltip" data-placement="right" data-original-title="{{=T('These files are served without processing, your images go here')}}">
{{=helpicon()}}
<span>{{=T("These files are served without processing, your images go here")}}</span>
</a><span id="static" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="static_inner" class="component_contents">
{{if not statics:}}<p><strong>{{=T("There are no static files")}}</strong></p>{{else:}}
<ul class="unstyled act_edit">
{{
{{
path=[]
for file in statics+['']:
items=file.split('/')
@@ -301,89 +308,88 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
path.append(file_path[len(path)])
thispath = regex_space.sub('-', 'static__'+'__'.join(path))
}}
<li class="folder"><i>&nbsp;</i>
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else:
path = path[:-1]
}}
</ul>
</li>
<li class="folder"><i>&nbsp;</i>
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else:
path = path[:-1]
}}
</ul></li>
{{
pass
pass
if filename:
}}
<li>
<span class="filetools controls">
{{=editfile('static',file, dict(id="static"))}} {{=deletefile([app,'static',file], dict(id="static",id2="static"))}}
</span>
<span class="file">
<a href="{{=URL(a=app,c='static',f=file)}}">{{=filename}}</a>
</span>
</li>{{
pass
pass
}}
<li>
<span class="filetools controls">
{{=editfile('static',file, dict(id="static"))}} {{=deletefile([app,'static',file], dict(id="static",id2="static"))}}
</span>
<span class="file">
<a href="{{=URL(a=app,c='static',f=file)}}">{{=filename}}</a>
</span>
</li>{{
pass
pass
}}
</ul>
{{pass}}
<div class="silver rounded padded">
<button onclick="jQuery('#form5').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
<div class="controls formfield">
<button onclick="jQuery('#form5').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button>
<div id="form5" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/static/' % app, 'static')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/static/' % app, 'static')}}</div>
</div>
</div>
</div>
</div>
<!-- MODULES -->
<h5 id="_modules">
<label class="component" for="modules_inner" data-tooltip="{{=T('Additional code for your application')}}">{{=T("Modules")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="modules_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_modules" rel="pagebookmark">
<span class="component" onclick="collapse('modules_inner');">{{=T("Modules")}}</span>
<a href="#modules" rel="tooltip" data-placement="right" data-original-title="{{=T('Additional code for your application')}}">
{{=helpicon()}}
<span>{{=T("Additional code for your application")}}</span>
</a><span id="modules" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="modules_inner" class="component_contents">
{{if not modules:}}<p><strong>{{=T("There are no modules")}}</strong></p>{{else:}}
<ul class="unstyled act_edit">
{{for m in modules:}}
{{id="modules__"+m.replace('/','__').replace('.','__')}}
<li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span>
{{=editfile('modules',m,dict(id=id))}}
{{if m!='__init__.py':}}
{{=deletefile([app, 'modules', m], dict(id=id, id2='modules'))}}
{{pass}}
</span>
<span class="file">
{{=peekfile('modules',m, dict(id=id))}}
</span>
</li>
{{pass}}
{{for m in modules:}}
{{id="modules__"+m.replace('/','__').replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetols controls">
{{=editfile('modules',m,dict(id=id))}}
{{if m!='__init__.py':}}
{{=deletefile([app, 'modules', m], dict(id=id, id2='modules'))}}
{{pass}}
</span>
<span class="file">
{{=peekfile('modules',m, dict(id=id))}}
</span>
</li>
{{pass}}
</ul>
{{pass}}
<div class="silver rounded padded">
<button onclick="jQuery('#form6').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
<div class="controls formfield">
<button onclick="jQuery('#form6').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button>
<div id="form6" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
</div>
</div>
</div>
</div>
<!-- PRIVATE -->
<h5 id="_private">
<label class="component" for="private_inner" data-tooltip="{{=T('These files are not served, they are only available from within your app')}}">{{=T("Private files")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="private_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_private" rel="pagebookmark">
<span class="component" onclick="collapse('private_inner');">{{=T("Private files")}}</span>
<a href="#private" rel="tooltip" data-placement="right" data-original-title="{{=T('These files are not served, they are only available from within your app')}}">
{{=helpicon()}}
<span>{{=T("These files are not served, they are only available from within your app")}}</span>
</a><span id="private" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="private_inner" class="component_contents">
{{if not privates:}}<p><strong>{{=T("There are no private files")}}</strong></p>{{else:}}
<ul class="unstyled act_edit">
{{
{{
path=[]
for file in privates+['']:
items=file.split('/')
@@ -394,74 +400,72 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
path.append(file_path[len(path)])
thispath='private__'+'__'.join(path)
}}
<li class="folder">
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else:
path = path[:-1]
}}
</ul>
</li>
{{
<li class="folder">
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else:
path = path[:-1]
}}
</ul>
</li>
{{
pass
pass
if filename:
}}
<li>
<span class="filetools controls">
{{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}}
</span>
<span class="file">
{{=peekfile('private',file, dict(id="private"))}}
</span>
</li>{{
pass
pass
}}
}}
<li>
<span class="filetools controls">
{{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}}
</span>
<span class="file">
{{=peekfile('private',file, dict(id="private"))}}
</span>
</li>{{
pass
pass
}}
{{pass}}
</ul>
<div class="silver rounded padded">
<button onclick="jQuery('#form7').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
<div class="controls formfield">
<button onclick="jQuery('#form7').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button>
<div id="form7" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div>
</div>
</div>
</div>
</div>
<!-- PLUGINS -->
<h5 id="_plugins">
<label class="component" for="plugins_inner" data-tooltip="{{=T('To create a plugin, name a file/folder plugin_[name]')}}">{{=T("Plugins files")}}</label>
</h5>
<div class="accordion">
<input type="checkbox" id="plugins_inner" checked>
<div>
<a href="#" class="right btn rounded small yellow">top</a>
<h3 id="_plugins" rel="pagebookmark">
<span class="component" onclick="collapse('plugins_inner');">{{=T("Plugins")}}</span>
<a href="#plugins" rel="tooltip" data-placement="right" data-original-title="{{=T('To create a plugin, name a file/folder plugin_[name]')}}">
{{=helpicon()}}
<span>{{=T("To create a plugin, name a file/folder plugin_[name]")}}</span>
</a><span id="plugins" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a>
</h3>
<div id="plugins_inner" class="component_contents">
{{if plugins:}}
<ul class="unstyled act_edit">
{{for plugin in plugins:}}
{{id="plugins__"+plugin.replace('/','__').replace('.','__')}}
<li id="{{=id}}">
{{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin], vars=dict(id=id, id2='plugins')))}}
</li>
{{pass}}
{{for plugin in plugins:}}
{{id="plugins__"+plugin.replace('/','__').replace('.','__')}}
<li id="{{=id}}">
{{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin], vars=dict(id=id, id2='plugins')))}}
</li>
{{pass}}
</ul>
{{else:}}
<p><strong>{{=T('There are no plugins')}}</strong></p>
{{pass}}
<div class="controls comptools">
{{=button(URL(c="default", f="plugins", args=[app,]), T('Download plugins from repository'))}}
{{=button(URL(c="default", f="plugins", args=[app,]), T('Download plugins from repository'))}}
</div>
<div class="silver rounded padded">
<button onclick="jQuery('#form8').slideToggle()" class="btn rounded small">{{=T('Upload')}}</button>
<div class="controls formfield">
<button onclick="jQuery('#form8').slideToggle()" class="btn btn-mini">{{=T('Upload')}}</button>
<div id="form8" class="row-fluid" style="display:none">
<div class="row-fluid">
<div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
@@ -486,7 +490,6 @@ jQuery(document).ready(function(){
if(code==13) filter_files();
});
jQuery('#search_start').click(function(e){ filter_files(); });
});
</script>
});
</script>
<!-- end "design" block -->
+1 -1
View File
@@ -32,7 +32,7 @@ def file_create_form(location, anchor=None, helptext=""):
<!-- begin "edit" block -->
{{
def shortcut(combo, description):
return XML('<li><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
return XML('<li class="span5"><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
def listfiles(app, dir, regexp='.*\.py$'):
files = sorted(
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
+13 -12
View File
@@ -1,22 +1,23 @@
{{extend 'layout.html'}}
{{block sectionclass}}login{{end}}
<!-- begin "index" block -->
<h2>web2py&trade; {{=T('Web Framework')}}</h2>
<div class="twothirds padded lifted">
<h3>{{=T('Login to the Administrative Interface')}}</h3>
<div class="form row-fluid">
{{if request.is_https or request.is_local:}}
<form action="{{=URL(r=request)}}" method="post" class="span4 well">
<h5>{{=T('Login to the Administrative Interface')}}</h5>
<label class="spaced" for="password">{{=T('Administrator Password:')}}</label>
<input class="spaced" type="password" name="password" id="password"/>
<input class="spaced" type="hidden" name="send" value="{{=send}}"/>
<button class="spaced" type="submit" name="login">{{=T('Login')}}</button>
</form>
<form action="{{=URL(r=request)}}" method="post" class="span4 well">
<label for="password">{{=T('Administrator Password:')}}</label>
<input type="password" name="password" id="password"/>
<input type="hidden" name="send" value="{{=send}}"/>
<div class="controls"><button type="submit" name="login" class="btn">{{=T('Login')}}</button></div>
</form>
{{else:}}
<p class="help span7 alert alert-block alert-warning">{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}</p>
{{pass}}
</div>
<script type="text/javascript">
jQuery(document).ready(function(){
jQuery("#password").focus();
});
jQuery(document).ready(function(){
jQuery("#password").focus();
});
</script>
<!-- end "index" block -->
<!-- end "index" block -->
+81 -83
View File
@@ -1,19 +1,19 @@
{{extend 'layout.html'}}
{{import os, glob}}
{{block sectionclass}}site{{end}}
<!-- begin "site" block -->
<div class="container">
<div class="twothirds">
<div class="padded">
<div class="row-fluid">
<div class="applist f60 span7">
<div class="applist_inner">
<h2>{{=T("Installed applications")}}</h2>
<table>
<tbody>
{{for a in apps:}}
<tr>{{buttons = []}}
<td>
<table width="100%" class="table">
{{for a in apps:}}
<tr>{{buttons = []}}
<td>
{{if a==request.application:}}
<a class="btn rounded gray">{{=a}} ({{=T('currently running')}})</a>
<h4 class="currentapp">{{=a}} ({{=T('currently running')}})</h4>
{{else:}}
<a class="btn rounded orange" href="{{=URL(a,'default','index')}}">{{=a}}</a>
<h4 class="editableapp">{{=A(a,_href=URL(a,'default','index'))}}</h4>
{{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}}
{{if not os.path.exists('applications/%s/compiled' % a):}}
{{buttons.append((URL('design',args=a), T("Edit")))}}
@@ -43,30 +43,28 @@
{{if a!=request.application:}}
{{buttons.append((URL('uninstall',args=a), T("Uninstall")))}}
{{pass}}
</td>
<td>
<ul class="menu">
<li>
<a class="btn white rounded">{{=T('Manage')}}</a>
<ul>
{{for link,name in buttons:}}
{{=LI(A(name,_href=link))}}
{{pass}}
</ul>
</li>
</ul>
</td>
<td>
{{=button_enable(URL('enable',args=a), a) if a!='admin' else ''}}
</td>
</tr>
</td>
<td>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
{{=T('Manage')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{{for link,name in buttons:}}
{{=LI(A(name,_href=link))}}
{{pass}}
</tbody>
</ul>
</div>
{{=button_enable(URL('enable',args=a, hmac_key=session.hmac_key), a) if a!='admin' else ''}}
</td>
</tr>
{{pass}}
</table>
</div>
</div> <!-- /applist -->
<div class="third black">
<div class="padded">
<div class="sidebar fl60 span5">
<div class="sidebar_inner controls well well-small">
<!-- CHANGE ADMIN PWD -->
<div class="pwdchange pull-right">
{{if MULTI_USER_MODE:}}
@@ -79,77 +77,77 @@
{{if is_manager():}}
<!-- VERSION -->
<div class="box">
<h6>{{=T("Version")}}</h6>
<p>
<tt>{{=myversion}}</tt><br/>
{{running_on = T("Running on %s", request.env.server_software or 'Unknown')}}
({{="%s, Python %s" % (running_on, myplatform)}})
</p>
<p id="check_version" class="row-buttons">
{{if session.check_version:}}
{{=T('Checking for upgrades...')}}
<script>ajax('{{=URL('check_version')}}',[],'check_version');</script>
{{session.check_version=False}}
{{else:}}
{{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}}
{{pass}}
</p>
{{if session.is_mobile=='auto':}}
<p>{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
{{pass}}
<h4>{{=T("Version")}}</h4>
<p>
<tt>{{=myversion}}</tt><br/>
{{running_on = T("Running on %s", request.env.server_software or 'Unknown')}}
({{="%s, Python %s" % (running_on, myplatform)}})
</p>
<p id="check_version" class="row-buttons">
{{if session.check_version:}}
{{=T('Checking for upgrades...')}}
<script>ajax('{{=URL('check_version')}}',[],'check_version');</script>
{{session.check_version=False}}
{{else:}}
{{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}}
{{pass}}
</p>
{{if session.is_mobile=='auto':}}
<p>{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
{{pass}}
</div> <!-- /VERSION -->
{{pass}}
{{if MULTI_USER_MODE and is_manager():}}
<!-- MULTI_USER_INTERFACE -->
<div class="box">
<h6>{{=T("Multi User Mode")}}</h6>
<p class="row-buttons">
{{=button(URL('bulk_register'),T('Bulk Register'))}}
{{=button(URL('manage_students',vars={'order':'auth_user.id'}),T('Manage Students'))}}
</p>
<h4>{{=T("Multi User Mode")}}</h4>
<p class="row-buttons">
{{=button(URL('bulk_register'),T('Bulk Register'))}}
{{=button(URL('manage_students',vars={'order':'auth_user.id'}),T('Manage Students'))}}
</p>
</div> <!-- /MULTI_USER_INTERFACE -->
{{pass}}
<!-- SCAFFOLD APP -->
<div class="box">
<h6>{{=T("New simple application")}}</h6>
{{=form_create.custom.begin}}
{{=LABEL(T("Application name:"))}}
{{=form_create.custom.widget.name}}
<div class="controls"><button type="submit" class="btn">{{=T('Create')}}</button></div>
{{=form_create.custom.end}}
<h4>{{=T("New simple application")}}</h4>
{{=form_create.custom.begin}}
{{=LABEL(T("Application name:"))}}
{{=form_create.custom.widget.name}}
<div class="controls"><button type="submit" class="btn">{{=T('Create')}}</button></div>
{{=form_create.custom.end}}
</div> <!-- /SCAFFOLD APP -->
<!-- UPLOAD PACKAGE -->
<div class="box">
<h6>{{=T("Upload and install packed application")}}</h6>
{{=form_update.custom.begin}}
<label for="appupdate_name">{{=T("Application name:")}}</label>
{{=form_update.custom.widget.name}}
<label for="appupdate_file">{{=T("Upload a package:")}}</label>
{{=form_update.custom.widget.file}}
<label for="appupdate_url">{{=T("Or Get from URL:")}}</label>
{{=form_update.custom.widget.url}}<small class="help-block">({{=T('can be a git repo')}})</small>
<div class="controls">
<label class="checkbox">
{{=form_update.custom.widget.overwrite}} {{=T("Overwrite installed app")}}
</label>
<button type="submit" class='btn'>{{=T('Install')}}</button>
</div>
{{=form_update.custom.end}}
<h4>{{=T("Upload and install packed application")}}</h4>
{{=form_update.custom.begin}}
<label for="appupdate_name">{{=T("Application name:")}}</label>
{{=form_update.custom.widget.name}}
<label for="appupdate_file">{{=T("Upload a package:")}}</label>
{{=form_update.custom.widget.file}}
<label for="appupdate_url">{{=T("Or Get from URL:")}}</label>
{{=form_update.custom.widget.url}}<small class="help-block">({{=T('can be a git repo')}})</small>
<div class="controls">
<label class="checkbox">
{{=form_update.custom.widget.overwrite}} {{=T("Overwrite installed app")}}
</label>
<button type="submit" class='btn'>{{=T('Install')}}</button>
</div>
{{=form_update.custom.end}}
</div> <!-- /UPLOAD PACKAGE -->
<!-- DEPLOY ON GAE -->
<div class="box">
<h6>{{=T("Deploy")}}</h6>
<p class="row-buttons">
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
{{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
</p>
<h4>{{=T("Deploy")}}</h4>
<p class="row-buttons">
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
{{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
</p>
</div> <!-- /DEPLOY ON GAE -->
<!-- APP WIZARD -->
<div class="box">
<h6>{{=T("New application wizard")}}</h6>
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
{{=T("(requires internet access, experimental)")}}</p>
<h4>{{=T("New application wizard")}}</h4>
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
{{=T("(requires internet access, experimental)")}}</p>
</div> <!-- /APP WIZARD -->
<!-- TWITTER TIMELINE -->
<div class="box twitter-timeline">
+112 -67
View File
@@ -1,69 +1,114 @@
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
th, td {color: black}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
[type=submit], [type=button] {border-radius:5px!important;padding:5px 10px;margin-top:10px}
</style>
{{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}}
{{include "web2py_ajax.html"}}
</head>
<body class="black">
<header class="black padded">
<div class="container middle max900">
<div class="fill middle">
<label class="ham padded fa fa-bars" for="menu"></label>
<div class="burger accordion">
<input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}}
</div>
</div>
</div>
</header>
{{if response.flash:}}
<div class="w2p_flash">
{{=response.flash}}
</div>
{{pass}}
<main class="white">
<div class="hidden">{{block sectionclass}}design{{end}}</div>
<div class="container max900">
{{if left_sidebar_enabled:}}
<div class="quarter padded">{{block left_sidebar}}{{end}}</div>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="P3P" content="CP=\"IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA\"" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{=response.title or URL()}}</title>
{{
response.files.append(URL('static','css/bootstrap.min.css'))
response.files.append(URL('static','css/bootstrap_essentials.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
}}
{{include 'web2py_ajax.html'}}
</head>
<body class="{{=T('direction: ltr') == 'direction: rtl' and 'RTLbody' or ''}} {{block sectionclass}}home{{end}}">
<!-- NAVBAR
============== -->
<div id="header" class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<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>
</button>
<div id="start" class="brand_wrapper">
<a href="{{=URL('default', 'index')}}" class="button brand" ><span>web2py&trade; {{=T('administrative interface')}}</span></a>
</div>
<div class="nav-collapse">
{{if response.menu is not None:}}
<ul id="menu" class="nav pull-right">
{{for _name,_active,_link in response.menu:}}
<li>{{=A(SPAN(_name), _href=_link, _class=_active and 'button select' or 'button')}}</li>
{{pass}}
</ul>
{{pass}}
</div><!--/.nav-collapse -->
</div><!-- /container-fluid -->
</div><!-- /navbar-inner -->
</div><!-- /#header -->
<!-- MAIN
=========== -->
<div id="{{=globals().get('main_id', 'main')}}" class="container-fluid">
<div id="main_inner" class="row-fluid">
<div class="span12">
<div class="w2p_flash alert">{{=response.flash or ''}}</div>
{{include}}
</div><!-- /main span12 -->
</div><!-- /main row-fluid -->
</div><!-- /#main -->
<!-- FOOTER
============== -->
{{block footer}}
<footer id="footer" class="fixed">
<p><span>{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}&trade; {{=T('created by')}} Massimo Di Pierro &copy;2007-{{=request.now.year}}
{{if hasattr(T,'get_possible_languages_info'):}}
- {{=T('Admin language')}}</span>
<select name="adminlanguage" onchange="var date = new Date();cookieDate=date.setTime(date.getTime()+(100*24*60*60*1000));document.cookie='adminLanguage='+this.options[this.selectedIndex].id+'; expires='+cookieDate+'; path=/';window.location.reload()">
{{for langinfo in sorted([(code,info[1]) for code,info in T.get_possible_languages_info().iteritems() if code != 'default']):}}
<option {{=T.accepted_language==langinfo[0] and 'selected' or ''}} {{='id='+langinfo[0]}} >{{=langinfo[1]}}</option>
{{pass}}
</select>
{{else:}}
</span>{{pass}}
</p>
</footer><!-- /#footer -->
{{end}}
<!-- BS JAVASCRIPT
====================== -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script type="text/javascript">
jQuery(document).ready(function(){
jQuery("[rel=tooltip]").tooltip();
jQuery(":input").attr("autocomplete","off");
});
</script>
<script>
// ====================
// upload input mask
// ====================
function FileSelectHandler(e) {
e.stopPropagation();
var filename = e.target.value.split(/\\|\//).pop();
jQuery('#fileselect>span').removeClass('txtPlaceholder').text(filename)
}
jQuery(document).ready(function(){
var iupload = jQuery('#appupdate_file');
var ow = 300, oh = 20;
var iplaceholder = jQuery('<span class="txtPlaceholder">{{=T("no package selected")}}</span>'),
iuploadbtn = jQuery('<button class="btn btn-inverse btn-mini uploadbtn"><i class="icon-white icon-circle-arrow-up"></i></button>');
iupload
.addClass('masked')
.wrap('<div id="fileselect" style="width:'+ow+'px;height:'+oh+'px"></div>')
.on('change', function(event){FileSelectHandler(event)});
jQuery('#fileselect').append(iplaceholder, iuploadbtn);
});
</script>
{{if request.function in ('index','site'):}}
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank">
<!-- http://webchat.freenode.net/?channels=web2py" //-->
<img src="{{=URL('static','images/questions.png')}}" />
</a>
{{pass}}
<div class="{{=middle_column}} padded">{{include}}</div>
{{if right_sidebar_enabled:}}
<div class="quarter padded">{{block right_sidebar}}{{end}}</div>
{{pass}}
</div>
</main>
<footer class="black">
<div class="container padded max900">
<div class="fill">
Copyright @ 2016 - Powered by Web2py
</div>
</div>
</footer>
</body>
<script>
// prevent android horizontal scrolling
window.addEventListener("scroll", function(){window.scroll(0, window.pageYOffset);}, false);
</script>
</body>
</html>
@@ -3,6 +3,7 @@
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
var w2p_ajax_disable_with_message = "{{=T('Working...')}}";
var ajax_error_500 = '{{=T.M('An error occured, please [[reload %s]] the page') % URL(args=request.args, vars=request.get_vars) }}'
//--></script>
{{
+8 -8
View File
@@ -10,7 +10,7 @@ session.forget()
cache_expire = not request.is_local and 300 or 0
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
# @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def index():
return response.render()
@@ -19,14 +19,13 @@ def index():
def what():
import urllib
try:
images = XML(urllib.urlopen(
'http://www.web2py.com/poweredby/default/images').read())
images = XML(urllib.urlopen('http://www.web2py.com/poweredby/default/images').read())
except:
images = []
return response.render(images=images)
#@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
# @cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def download():
return response.render()
@@ -74,14 +73,15 @@ def license():
filename = os.path.join(request.env.gluon_parent, 'LICENSE')
return response.render(dict(license=MARKMIN(read_file(filename))))
def version():
if request.args(0)=='raw':
if request.args(0) == 'raw':
return request.env.web2py_version
from gluon.fileutils import parse_version
(a, b, c, pre_release, build) = parse_version(request.env.web2py_version)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % (
a,b,c,build.year,build.month,build.day,
build.hour,build.minute,build.second,pre_release)
return 'Version %i.%i.%i (%.4i-%.2i-%.2i %.2i:%.2i:%.2i) %s' % \
(a, b, c, build.year, build.month, build.day, build.hour, build.minute, build.second, pre_release)
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def examples():
@@ -35,12 +35,6 @@ def hello6():
response.flash = 'Hello World in a flash!'
return dict(message=T('Hello World'))
def status():
""" page that shows internal status"""
return dict(toolbar=response.toolbar())
def redirectme():
""" redirects to /{{=request.application}}/{{=request.controller}}/hello3 """
@@ -27,4 +27,4 @@ def xml():
def beautify():
return dict(message=BEAUTIFY(request))
return dict(message=BEAUTIFY(dict(a=1,b=[2,3,dict(hello='world')])))
+6 -6
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
response.menu = [
(T('Home'), False, URL('default', 'index')),
(T('About'), False, URL('default', 'what')),
(T('Download'), False, URL('default', 'download')),
(T('Docs & Resources'), False, URL('default', 'documentation')),
(T('Support'), False, URL('default', 'support')),
(T('Contributors'), False, URL('default', 'who'))]
(T('Home'), request.controller == 'default' and request.function == 'index', URL('default', 'index')),
(T('About'), request.controller == 'default' and request.function == 'what', URL('default', 'what')),
(T('Download'), request.controller == 'default' and request.function == 'download', URL('default', 'download')),
(T('Docs & Resources'), request.controller == 'default' and request.function == 'documentation', URL('default', 'documentation')),
(T('Support'), request.controller == 'default' and request.function == 'support', URL('default', 'support')),
(T('Contributors'), request.controller == 'default' and request.function == 'who', URL('default', 'who'))]
#########################################################################
## Changes the menu active item
+3 -1
View File
@@ -1 +1,3 @@
session.connect(request,response,cookie_key='yoursecret')
from gluon.utils import web2py_uuid
cookie_key = cache.ram('cookie_key',lambda: web2py_uuid(),None)
session.connect(request,response,cookie_key=cookie_key)
@@ -10,6 +10,7 @@
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
- [[web2py Reference Project http://www.web2pyref.com/]]
- [[An advanced tutorial https://milesm.pythonanywhere.com/wiki]]
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
@@ -19,9 +20,9 @@
#### Code
- [[web2pyslices (recipes) http://www.web2pyslices.com popup]]
- [[Layouts http://www.web2py.com/layouts popup]]
- [[Dashboard welcome app https://github.com/mjbeller/web2py-starter]]
- [[stupid.css theme https://github.com/mdipierro/web2py-welcome-theme-stupid]]
- [[Plugins http://www.web2py.com/plugins popup]]
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
- [[Appliances http://www.web2py.com/appliances popup]]
- [[web2py utils http://packages.python.org/web2py_utils/ popup]]
- [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]]
+69 -20
View File
@@ -1,20 +1,69 @@
@import url(http://fonts.googleapis.com/css?family=Economica);
@@import url(http://fonts.googleapis.com/css?family=Belleza);
body { font-family: Arial, Helvetica; }
a, a:visited, a:hover, h1,h2,h3,h4,h5 {color: #658883}
a.btn-danger, a.btn-warning, a.btn-success {color:white}
h1,h2,h3,h4,h5 { font-family: "Economica", Arial, Helevtica; }
body {
background: url('../images/stripes.png') repeat-x;
}
#header {
margin-top: 40px;
}
.btn-180 {
width: 180px;
}
.page-header {
border-bottom: 0;
}
/* Gray the black as suggested by Anthony */
h1,h2,h3,h4,h5,h6 {color: rgb(35, 35, 35); text-transform:none}
.black {
color: rgb(35, 35, 35);
background-color: rgb(35, 35, 35);
}
/* Spacing between thead and tbody */
/* Ref: http://stackoverflow.com/questions/9258754/spacing-between-thead-and-tbody */
tbody:before {
content: "-";
display: block;
line-height: 1em;
color: transparent;
}
/* Improve buttons in download page */
th, td {padding: 0}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
/* Improve CODE() display though padding has no effect as some PRE are hardcoded somewhere can't find it */
/* padding of 10px should make it... */
pre {background-color: rgb(35, 35, 35)!important; border-radius:5px; color:white; padding: 10px}
/* Improve buttons in download page */
a.btn.btn180 {padding:10px; font-size:1.2em; width:200px}
.menu .web2py-menu-active a {
color: #26a69a;
}
.spaced-vertical {
margin: 0 0.5em 0.5em 0;
}
.btn:hover,
a.noeffect img:hover {
transition: scale .5s;
transform: scale(1.05);
}
.btn,
a.noeffect img {
transition: all .2s ease-in-out;
}
/* Lower saturation of color #26a69a - 20 points lower */
/* The below change to color #26a69a should come before other color change or they override all buttons background-color */
/* The color should maybe change at stupid.css level as it herited from there also in stupid.css it would be better
to define this color at one place actually color is defined all over the place */
a {color:#47a69d}
.btn, button, [type=button], [type=submit] {background-color:#47a69d}
.progress .determinate {background-color:#47a69d}
.progress .indeterminate {background-color:#47a69d}
a:not(.btn):not(.noeffect):hover {color:#47a69d}
a:not(.btn):not(.noeffect):after {background-color:#47a69d}
.tags > span {background-color:#47a69d}
.tags.dismissible > span.off:hover {background-color:#47a69d}
.aquamarine{background-color:#47a69d}
/* Lower the saturation of 20 points */
.green {background-color: #58cc65}
.yellow {background-color: #ffe333}
.red {background-color: #cc4229}
+58 -58
View File
@@ -5,8 +5,9 @@
************/
/*** basic styles ***/
* {border:0; margin:0; padding:0; font-familiy:Helvetica}
html, body {max-width: 100vw !important;overflow-x: hidden !important}
html {box-sizing:border-box;}
*, *:after, *:before {border:0; margin:0; padding:0; box-sizing:inherit;}
html, body {max-width: 100vw; overflow-x: hidden}
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
p, li {margin-bottom:0.5em}
p {text-align:justify}
@@ -14,7 +15,7 @@ label, strong {font-weight:bold}
ul {list-style-type:none; padding-left:20px}
a {text-decoration:none; color:#26a69a; white-space:nowrap}
a:hover {cursor:pointer}
h1,h2,h3,h4,h5,h6{font-weight:strong; text-transform:uppercase}
h1,h2,h3,h4,h5,h6{font-weight:bold; text-transform:uppercase}
h1{font-size:4em; margin:1.0em 0 0.25em 0}
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
@@ -23,15 +24,13 @@ h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9}
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
thead tr {background-color:#f1f1f1}
tbody tr {border-bottom:2px solid #f1f1f1}
th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
td, th {padding: 5px; text-align: left; vertical-align:top}
thead th {vertical-align:bottom}
tbody th {vertical-align:top}
header, footer {with:100%}
header, main, footer {display:block; with:100%} /* IE fix */
@media (max-width:599px) {
@media all and (max-width:599px) {
h1{font-size:2em}
h2{font-size:1.8em}
h3{font-size:1.6em}
@@ -41,22 +40,22 @@ header, footer {with:100%}
}
/*** buttons ***/
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; line-height:2.4em; background-color:#26a69a; color:white}
.btn, button, [type=button], [type=submit] {padding:0.5em 1em; margin:0 0.5em 0.5em 0; display:inline-block; background-color:#26a69a; color:white}
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em; line-height:1.5em}
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
.btn.small, table .btn {padding:0.25em 0.5em; font-size:0.8em}
.btn.large {padding:1em 2em; font-size:1.2em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
.center {text-align:center !important; margin-left:auto; margin-right:auto}
.padded {padding:10px 20px}
.center {text-align:center; margin-left:auto; margin-right:auto}
.center>div {text-align:left}
.right {right:0; text-align:right}
.middle div {vertical-align:middle !important}
.bottom div {vertical-align:bottom !important}
.xscroll {overflow-x:scroll; width:100%}
.yscroll {overflow-y:scroll; width:100%}
.middle div {vertical-align:middle}
.bottom div {vertical-align:bottom}
.xscroll {overflow-x:scroll}
.yscroll {overflow-y:scroll}
.nowrap {white-space:nowrap; overflow-x:hidden}
.fill {width:100%}
.lifted {box-shadow:5px 5px 10px #666}
@@ -66,18 +65,20 @@ header, footer {with:100%}
.hidden {display:none}
/*** forms ***/
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outline:none}
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd}
input, textarea, select, button {font-size:12px}
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd; width:100%;background-color:transparent}
input, textarea, select, button, .btn {font-size:12px}
input:not([type]):hover, input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
input:invalid, input.error {background:#cc1f00;color:white}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; vertical-align:top}
.container>.fill {width:100%; margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters {display:inline-block; padding: 0 20px 0 0; vertical-align:top}
.container>.fill{display: inline-block}
.container img, .container video {max-width:100%}
@media (min-width:800px) {
@media all and (min-width:800px) {
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
.quarter {width:25%; margin-right:-5px}
.half {width:50%; margin-right:-10px}
@@ -85,7 +86,7 @@ input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit
.twothirds {width:66.66%; margin-right:-13.33px}
.threequarters {width:75%; margin-right:-15px}
}
@media (min-width:600px) and (max-width:799px) {
@media all and (min-width:600px) and (max-width:799px) {
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.threequarters.compressible {width:75%; margin-right:-15px}
@@ -94,7 +95,7 @@ input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit
.twothirds {width:66.66%; margin-right:-13.33px}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
}
@media (max-width:599px) {
@media all and (max-width:599px) {
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
@@ -114,7 +115,7 @@ input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit
display:block;
width:120%;
background-color:#acece6;
border-radius:0 !important;
border-radius:0;
background-clip:padding-box;
overflow:hidden;
}
@@ -189,20 +190,20 @@ input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit
.menu ul {background:white; border:1px solid #e1e1e1; visibility:hidden; opacity:0; position:absolute; top:110%; padding:0; z-index:1000; transition:all 0.2s ease-out; list-style-type:none; box-shadow:5px 5px 10px #666}
.menu ul a {padding:10px 15px; color:#333; font-weight:700; font-size:12px; line-height:16px; display: block}
.menu ul li {float:none; width:200px}
.menu ul ul {top:0; left:80%; z-index:2000}
.menu ul ul {top:0; left:80%; z-index:1100}
.menu li:hover > ul {visibility:visible; opacity:1}
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
.menu.dark ul {background:black; border:1px solid black}
.menu.dark ul {background:#111111; border:1px solid #111111}
.menu.dark ul a {color:white}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:#111111}
@media (max-width:599px) {
@media all and (max-width:599px) {
header .menu li, header .menu ul {width: 100%}
header .menu.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px; z-index:2000}
header .menu ul ul {top:2.5em; left:-1px}
}
@media (min-width:600px) {
@media all and (min-width:600px) {
.ham {display:none!important}
.burger.accordion * {max-height:1000px; overflow:visible}
}
@@ -264,24 +265,30 @@ a:not(.btn):not(.noeffect):after {
/*** tooltips from http://codepen.io/trezy/pen/Khnzy ***/
[data-tooltip] {position:relative}
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:before {
border-bottom:.6em solid black;
border-bottom:.6em solid black;
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:hover:before {
border-bottom:.6em solid #111111;
border-bottom:.6em solid #111111;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
left:20px;
margin-top:1em;
left:0;
margin-top:12px;
z-index:2000;
}
[data-tooltip]:after {
[data-tooltip]:hover:after {
z-index:2000;
background-color:rgba(0,0,0,0.8);
border:4px solid rgba(0,0,0,0.8);
border-radius:7px;
color:white;
text-transform:none;
font-size: 12px;
content:attr(data-tooltip);
left:0;
top:2px;
margin-left:-20px;
margin-top:1.5em;
padding:5px 15px;
white-space:pre-wrap;
@@ -332,28 +339,21 @@ a:not(.btn):not(.noeffect):after {
transform: rotateY( 180deg );
}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
/**** tags ****/
.tags > span, .tags > span.off:hover {
height: 30px;
.tags > span {
padding: 4px 9px;
text-decoration: none;
margin: 0 5px 30px 0 !important;
white-space: nowrap;
color: white;
background-color: #26a69a;
border-radius: 5px;
line-height: 32px;
}
.tags.dismissible > span:not(.off):hover {
background-color: #ccc !important;
}
.tags.dismissible > span:not(.off):after {
content: " \f00d";
font-family: FontAwesome;
}
.tags > span.off {
background-color: #ccc;
font-size:12px;
margin: 2px 5px 2px 0;
display: inline-block;
}
.tags.dismissible > span:hover {opacity: 0.5}
.tags.dismissible > span:not(.off):after {content:" ✕"}
.tags > span.off {background-color: #ccc}
.tags.dismissible > span.off:hover {background-color:#26a69a}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f;color:white}.blue{background-color:#0074d9;color:white}.aqua{background-color:#7fdbff;color:#111111}.teal{background-color:#39cccc;color:white}.olive{background-color:#3d9970;color:white}.green{background-color:#2ecc40;color:white}.aquamarine{background-color:#26a69a;color:white}.lime{background-color:#01ff70;color:#111111}.yellow{background-color:#ffdc00;color:#111111}.orange{background-color:#ff851b;color:white}.red{background-color:#cc1f00;color:white}.fuchsia{background-color:#f012be;color:white}.pink{background-color:#ee6e73;color:white}.purple{background-color:#b10dc9;color:white}.maroon{background-color:#85144b;color:white}.white{background-color:#fff;color:#111111;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa;color:white}.silver{background-color:#f1f1f1;color:#111111}.black{background-color:#111111;color:white}.glass{background:rgba(255,255,255,0.5);color:#111111}
File diff suppressed because one or more lines are too long
@@ -1,11 +1,10 @@
{{extend 'layout.html'}}
<center>
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&amp;background=ffffff&amp;slideshow=1&amp;video_title=1&amp;video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
</center>
<div>
{{=get_content('main')}}
<center>
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&amp;background=ffffff&amp;slideshow=1&amp;video_title=1&amp;video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
</center>
{{=get_content('official')}}
{{=get_content('community')}}
{{=get_content('more')}}
@@ -17,7 +17,7 @@
<tbody>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
@@ -28,7 +28,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
@@ -37,7 +37,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
@@ -48,7 +48,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
@@ -69,9 +69,9 @@
<h3>Instructions</h3>
<p>After download, unzip it and click on web2py.exe (windows) or web2py.app (osx).
To run from source, type:</p>
{{=CODE("python2.7 web2py.py",language=None,counter='>',_class='boxCode')}}
{{=CODE("python2.7 web2py.py", language=None, counter='>', _class='boxCode')}}
<p>or for more info type:</p>
{{=CODE("python2.7 web2py.py -h",language=None,counter='>',_class='boxCode')}}
{{=CODE("python2.7 web2py.py -h", language=None, counter='>', _class='boxCode')}}
<h3>Caveats</h3>
@@ -84,7 +84,7 @@
<p>Applications built with web2py can be released under any license the author wishes as long they do not contain web2py code. They can link unmodified web2py libraries and they can be distributed with official web2py binaries. In particular web2py applications can be distributed in closed source. The admin interface provides a button to byte-code compile.</p>
<p>It is fine to distribute web2py (source or compiled) with your applications as long as you make it clear in the license where your application ends and web2py starts.</p>
<p>web2py is copyrighted by Massimo Di Pierro. The web2py trademark is owned by Massimo Di Pierro.</p>
<a class="btn btn-small" href="{{=URL('license')}}">read more</a>
<a class="btn btn-small rounded" href="{{=URL('license')}}">read more</a>
<h3>Artwork</h3>
<center>
@@ -94,7 +94,6 @@ def status():
return dict(toobar=response.toolbar())
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here we are showing the request, session and response objects using the generic.html template.
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/status">status</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -278,7 +277,7 @@ def xml():
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
def beautify():
return dict(message=BEAUTIFY(request))
dict(message=BEAUTIFY(dict(a=1,b=[2,3,dict(hello='world')])))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can use BEAUTIFY to turn lists and dictionaries into organized HTML.
+10 -12
View File
@@ -3,22 +3,22 @@
<div class="container">
<div class="twothirds">
<div class="padded">
<h3>web2py<sup>TM</sup> Web Framework</h3>
<h3><img src="{{=URL('static/images', 'web2py_logo.png')}}"> Web Framework</h3>
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
<table width="100%">
<tr>
<td>
<a href="http://web2py.com/book">
<a class="noeffect" href="http://web2py.com/book">
<img src="{{=URL('static','images/book-5th.png')}}" />
</a>
</td>
<td>
<a href="https://vimeo.com/album/3016728">
<a class="noeffect" href="https://vimeo.com/album/3016728">
<img src="{{=URL('static','images/videos.png')}}" />
</a>
</td>
<td>
<a href="http://link.packtpub.com/SUlnrN">
<a class="noeffect" href="http://link.packtpub.com/SUlnrN">
<img src="{{=URL('static','images/book-recipes.png')}}" />
</a>
</td>
@@ -29,12 +29,15 @@
</div>
<div class="third">
<div class="padded center">
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img src="{{=URL('static','images/infoworld2012.jpeg')}}">
<a class="noeffect" href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img class="spaced-vertical" src="{{=URL('static','images/infoworld2012.jpeg')}}">
</a>
<a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now
</a>
<a class="btn rounded red fill" href="{{=URL('examples')}}">
Quick Examples
</a>
<a class="btn rounded red fill" href="https://www.pythonanywhere.com/try-web2py">
Try it now online
</a>
@@ -60,12 +63,7 @@
<div class="third">
<div class="padded">
<h5><a href="{{=URL('documentation')}}">Extensive Docs</a></h5>
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://dev.s-cubism.com/web2py_plugins" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://www.web2pyslices.com/home?content_type=Package" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
</div>
</div>
</div>
<div class="container">
<div class="fill padded">
<img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png">
</div>
</div>
@@ -32,7 +32,6 @@
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li>
<li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li>
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li>
@@ -82,6 +82,7 @@
</li><li>Keith Yang (openid)
</li><li><a href="http://dev.s-cubism.com/web2py_plugins">Kenji Hosoda</a> (plugins)
</li><li>Kyle Smith (javascript)
</li><li><a href="https://github.com/leonelcamara">Leonel Câmara</a>
</li><li><a href="http://blog.donews.com/limodou/">Limodou</a> (winservice)
</li><li><a href="https://github.com/lucasdavila">Lucas D'Ávila</a>
</li><li>Marc Abramowitz (tests and travis continuous integration)
@@ -99,6 +100,7 @@
</li><li>Michael Willis (shell)
</li><li>Michele Comitini (facebook)
</li><li>Michael Toomim (scheduler)
</li><li>Narendra Bhati (security)
</li><li>Nathan Freeze (admin design, IS_STRONG, DAL features, <a href="http://web2pyslices.com">web2pyslices.com</a>)
</li><li>Niall Sweeny (MSSQL support)
</li><li>Niccolo Polo (epydoc)
+10 -10
View File
@@ -1,21 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/examples.css')}}" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
th {color: black}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
</style>
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
{{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
@@ -28,7 +24,7 @@
<header class="black padded">
<div class="container middle max900">
<div class="fill middle">
<label class="ham padded fa fa-bars" for="menu"></label>
<label class="ham" for="menu"><i class="fa fa-bars padded"></i></label>
<div class="burger accordion">
<input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}}
@@ -51,6 +47,10 @@
<div class="quarter padded">{{block right_sidebar}}{{end}}</div>
{{pass}}
</div>
<div class="silver center padded">
<a class="fa fa-twitter" href="https://twitter.com/web2py/"></a>
<a class="fa fa-facebook" href="https://www.facebook.com/web2py/"></a>
</div>
</main>
<footer class="black">
<div class="container padded max900">
@@ -1,3 +0,0 @@
{{extend 'layout.html'}}
{{=toolbar}}
+7 -6
View File
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
#########################################################################
## This is a sample controller
## - index is the default action of any application
## - user is required for authentication and authorization
## - download is for downloading files uploaded in the db (does streaming)
#########################################################################
# -------------------------------------------------------------------------
# This is a sample controller
# - index is the default action of any application
# - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming)
# -------------------------------------------------------------------------
def index():
"""
+491 -480
View File
@@ -1,480 +1,491 @@
# coding: utf8
{
'!langcode!': 'cs-cz',
'!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}',
'%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(requires internet access, experimental)',
'(something like "it-it")': '(například "cs-cs")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}',
'About': 'O programu',
'About application': 'O aplikaci',
'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci',
'Admin design page': 'Admin design page',
'Admin language': 'jazyk rozhraní',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'An error occured, please %s the page',
'and rename it:': 'a přejmenovat na:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Application',
'application "%s" uninstalled': 'application "%s" odinstalována',
'application compiled': 'aplikace zkompilována',
'Application name:': 'Název aplikace:',
'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'arguments': 'arguments',
'at char %s': 'at char %s',
'at line %s': 'at line %s',
'ATTENTION:': 'ATTENTION:',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět',
'Back to wizard': 'Back to wizard',
'Basics': 'Basics',
'Begin': 'Začít',
'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení',
'Buy this book': 'Koupit web2py knihu',
'Cache': 'Cache',
'cache': 'cache',
'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo',
'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné',
'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací',
'Change password': 'Změna hesla',
'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit',
'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta',
'code': 'code',
'Code listing': 'Code listing',
'collapse/expand all': 'vše sbalit/rozbalit',
'Community': 'Komunita',
'Compile': 'Zkompilovat',
'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka',
'continue': 'continue',
'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry',
'controllers': 'kontroléry',
'Copyright': 'Copyright',
'Count': 'Počet',
'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:',
'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým',
'Created On': 'Vytvořeno - kdy',
'crontab': 'crontab',
'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď',
'Current session': 'Aktuální relace',
'currently running': 'právě běží',
'currently saved or': 'uloženo nebo',
'customize me!': 'upravte mě!',
'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr',
'Database administration': 'Database administration',
'database administration': 'správa databáze',
'Date and Time': 'Datum a čas',
'day': 'den',
'db': 'db',
'DB Model': 'Databázový model',
'Debug': 'Ladění',
'defines tables': 'defines tables',
'Delete': 'Smazat',
'delete': 'smazat',
'delete all checked': 'smazat vše označené',
'delete plugin': 'delete plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení',
'Demo': 'Demo',
'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis',
'design': 'návrh',
'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti',
'direction: ltr': 'směr: ltr',
'Disable': 'Zablokovat',
'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán',
'docs': 'dokumentace',
'Documentation': 'Dokumentace',
"Don't know what to do?": 'Nevíte kudy kam?',
'done!': 'hotovo!',
'Download': 'Stáhnout',
'download layouts': 'stáhnout moduly rozvržení stránky',
'download plugins': 'stáhnout zásuvné moduly',
'E-mail': 'E-mail',
'Edit': 'Upravit',
'edit all': 'edit all',
'Edit application': 'Správa aplikace',
'edit controller': 'edit controller',
'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil',
'edit views:': 'upravit pohled:',
'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editing Plural Forms File',
'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Ticket chyby',
'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Exception %s': 'Exception %s',
'Exception instance attributes': 'Prvky instance výjimky',
'Expand Abbreviation': 'Expand Abbreviation',
'export as csv file': 'exportovat do .csv souboru',
'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'FAQ': 'Často kladené dotazy',
'File': 'Soubor',
'file': 'soubor',
'file "%(filename)s" created': 'file "%(filename)s" created',
'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s',
'Filename': 'Název souboru',
'filter': 'filtr',
'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Frames',
'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné',
'go!': 'OK!',
'Goto': 'Goto',
'graph model': 'graph model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny',
'Groups': 'Skupiny',
'Hello World': 'Ahoj světe',
'Help': 'Nápověda',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak jste se sem vlastně dostal?',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'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': 'import',
'Import/Export': 'Import/Export',
'includes': 'zahrnuje',
'Index': 'Index',
'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'inspect attributes',
'Install': 'Instalovat',
'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka',
'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod',
'Invalid email': 'Neplatný email',
'Invalid password': 'Nesprávné heslo',
'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek',
'Is Active': 'Je aktivní',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč',
'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'languages': 'jazyky',
'Languages': 'Jazyky',
'Last name': 'Příjme',
'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro',
'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku',
'Live Chat': 'Online pokec',
'loading...': 'nahrávám...',
'locals': 'locals',
'Locals##debug': 'Lokální proměnné',
'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně',
'Login': 'Přihlásit se',
'login': 'přihlásit se',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'logout': 'odhlásit se',
'Logout': 'Odhlásit se',
'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?',
'Manage': 'Manage',
'Manage Cache': 'Manage Cache',
'Menu Model': 'Model rozbalovací nabídky',
'Models': 'Modely',
'models': 'modely',
'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy',
'Modules': 'Moduly',
'modules': 'moduly',
'My Sites': 'Správa aplikací',
'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena',
'New Application Wizard': 'Nový průvodce aplikací',
'New application wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo',
'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit primitivní aplikaci',
'next': 'next',
'next 100 rows': 'dalších 100 řádků',
'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo',
'online designer': 'online návrhář',
'Online examples': 'Příklady online',
'Open new app in new window': 'Open new app in new window',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Or Get from URL:',
'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad',
'Other Plugins': 'Ostatní moduly',
'Other Recipes': 'Ostatní zásuvné moduly',
'Overview': 'Přehled',
'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované',
'pack plugin': 'pack plugin',
'password': 'heslo',
'Password': 'Heslo',
"Password fields don't match": 'Hesla se neshodu',
'Peeking at file': 'Peeking at file',
'Please': 'Prosím',
'Plugin "%s" in application': 'Plugin "%s" in application',
'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Plural Form #%s',
'Plural-Forms:': 'Množná čísla:',
'Powered by': 'Poháněno',
'Preface': 'Předmluva',
'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory',
'profile': 'profil',
'Project Progress': 'Vývoj projektu',
'Python': 'Python',
'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady',
'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána',
'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to',
'Record': 'Záznam',
'record does not exist': 'záznam neexistuje',
'Record ID': 'ID záznamu',
'Record id': 'id záznamu',
'refresh': 'obnovte',
'register': 'registrovat',
'Register': 'Zaregistrovat se',
'Registration identifier': 'Registrační identifikátor',
'Registration key': 'Registrační klíč',
'reload': 'reload',
'Reload routes': 'Znovu nahrát cesty',
'Remember me (for 30 days)': 'Zapamatovat na 30 dní',
'Remove compiled': 'Odstranit zkompilované',
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše',
'request': 'request',
'Reset Password key': 'Reset registračního klíče',
'response': 'response',
'restart': 'restart',
'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return',
'revert': 'vrátit se k původnímu',
'Role': 'Role',
'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s',
'Save': 'Uložit',
'Save file:': 'Save file:',
'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:',
'Semantic': 'Modul semantic',
'Services': 'Služby',
'session': 'session',
'session expired': 'session expired',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka',
'Singular Form': 'Singular Form',
'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:',
'skip to generate': 'skip to generate',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce',
'state': 'stav',
'Static': 'Static',
'static': 'statické soubory',
'Static files': 'Statické soubory',
'Statistics': 'Statistika',
'Step': 'Step',
'step': 'step',
'stop': 'stop',
'Stylesheet': 'CSS styly',
'submit': 'odeslat',
'Submit': 'Odeslat',
'successful': 'úspěšně',
'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'Table': 'tabulka',
'Table name': 'Název tabulky',
'Temporary': 'Dočasný',
'test': 'test',
'Testing application': 'Testing application',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je slovník, který se zobrazil v pohledu %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The Views': 'Pohledy (The Views)',
'There are no controllers': 'There are no controllers',
'There are no modules': 'There are no modules',
'There are no plugins': 'Žádné moduly nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'There are no static files',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no views': 'There are no views',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is the %(filename)s template': 'This is the %(filename)s template',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké',
'Traceback': 'Traceback',
'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'try something like',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'try view',
'Twitter': 'Twitter',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'Unable to check for upgrades',
'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat',
'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky',
'Update:': 'Upravit:',
'Upgrade': 'Upgrade',
'upgrade now': 'upgrade now',
'upgrade now to %s': 'upgrade now to %s',
'upload': 'nahrát',
'Upload': 'Upload',
'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
'User %(id)s Profile updated': 'Uživatel %(id)s upravil profil',
'User %(id)s Registered': 'Uživatel %(id)s se zaregistroval',
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno',
'variables': 'variables',
'Verify Password': 'Zopakujte heslo',
'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování',
'Videos': 'Videa',
'View': 'Pohled (View)',
'Views': 'Pohledy',
'views': 'pohledy',
'Web Framework': 'Web Framework',
'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'web2py upgrade',
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve web2py',
'Welcome to web2py!': 'Vítejte ve web2py!',
'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.',
'You are successfully running web2py': 'Úspěšně jste spustili web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You visited the url %s': 'Navštívili jste stránku %s,',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
}
# -*- coding: utf-8 -*-
{
'!langcode!': 'cs-cz',
'!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}',
'%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(vyžaduje internetové připojení, experimentální)',
'(something like "it-it")': '(například "cs-cz")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '@markmin\x01(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01An error occured, please [[reload %s]] the page': '@markmin\x01Došlo k chybě, prosím [[obnovte stránku %s]]',
'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Hledání: **%s** %%{soubor}',
'About': 'O programu',
'About application': 'O aplikaci',
'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci',
'admin': 'admin',
'Admin design page': 'Admin design page',
'Admin language': 'jazyk rozhraní',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'Došlo k chybě, prosím %s stránku',
'and rename it:': 'a přejmenovat na:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Aplikace',
'application "%s" uninstalled': 'application "%s" odinstalována',
'application compiled': 'aplikace zkompilována',
'Application name:': 'Název aplikace:',
'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'arguments': 'argumenty',
'at char %s': 'na pozici znaku %s',
'at line %s': 'na řádku %s',
'ATTENTION:': 'POZOR:',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět',
'Back to wizard': 'Zpátky do průvodce',
'Basics': 'Základy',
'Begin': 'Začít',
'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení',
'Buy this book': 'Koupit Web2py knihu',
"Buy web2py's book": 'Koupit Web2py knihu',
'Cache': 'Cache',
'cache': 'cache',
'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo',
'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné',
'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací',
'Change password': 'Změna hesla',
'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit',
'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta',
'code': 'kód',
'Code listing': 'Výpis kódu',
'collapse/expand all': 'vše sbalit/rozbalit',
'Community': 'Komunita',
'Compile': 'Zkompilovat',
'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka',
'Config.ini': 'Config.ini',
'continue': 'pokračovat',
'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry',
'controllers': 'kontroléry',
'Copyright': 'Copyright',
'Count': 'Počet',
'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:',
'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým',
'Created On': 'Vytvořeno - kdy',
'crontab': 'crontab',
'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď',
'Current session': 'Aktuální relace',
'currently running': 'právě běží',
'currently saved or': 'uloženo nebo',
'customize me!': 'upravte mě!',
'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr',
'Database administration': 'Administrace databáze',
'database administration': 'správa databáze',
'Date and Time': 'Datum a čas',
'day': 'den',
'db': 'db',
'DB Model': 'Databázový model',
'Debug': 'Ladění',
'defines tables': 'definuje tabulky',
'Delete': 'Smazat',
'delete': 'smazat',
'delete all checked': 'smazat vše označené',
'delete plugin': 'zrušit plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení',
'Demo': 'Demo',
'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis',
'design': 'návrh',
'Design': 'Design',
'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti',
'direction: ltr': 'směr: ltr',
'Disable': 'Zablokovat',
'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán',
'docs': 'dokumentace',
'Documentation': 'Dokumentace',
"Don't know what to do?": 'Kde najdu další informace ?',
'done!': 'hotovo!',
'Download': 'Stáhnout',
'download layouts': 'stáhnout moduly rozvržení stránky',
'download plugins': 'stáhnout zásuvné moduly',
'E-mail': 'E-mail',
'Edit': 'Upravit',
'edit all': 'editovat vše',
'Edit application': 'Správa aplikace',
'edit controller': 'editovat controller',
'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil',
'edit views:': 'upravit pohled:',
'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editování souboru množných čísel',
'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Ticket chyby',
'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Výjimka %(extype)s: %(exvalue)s',
'Exception %s': 'Výjimka %s',
'Exception instance attributes': 'Prvky instance výjimky',
'Expand Abbreviation': 'Expandovat zkratku',
'export as csv file': 'exportovat do .csv souboru',
'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'FAQ': 'Často kladené dotazy',
'File': 'Soubor',
'file': 'soubor',
'file "%(filename)s" created': 'soubor "%(filename)s" byl vytvořen',
'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s',
'Filename': 'Název souboru',
'filter': 'filtr',
'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Framy',
'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné',
'go!': 'OK!',
'Goto': 'Přejít na',
'graph model': 'grafický model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny',
'Groups': 'Skupiny',
'Hello World': 'Ahoj všichni',
'Help': 'Nápověda',
'Helping web2py': 'Podpořte Web2py',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak se Ti tato stránka vlastně zobrazila?',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'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.\r\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'import': 'import',
'Import/Export': 'Import/Export',
'includes': 'zahrnuje',
'Index': 'Index',
'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'prohlédnout atributy',
'Install': 'Instalovat',
'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka',
'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod',
'Invalid email': 'Neplatný email',
'Invalid password': 'Nesprávné heslo',
'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek',
'Is Active': 'Je aktiv',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč',
'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Key bindings pro ZenCoding Plugin',
'languages': 'jazyky',
'Languages': 'Jazyky',
'Last name': 'Příjmení',
'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro',
'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku',
'Live Chat': 'Online chat',
'loading...': 'nahrávám...',
'locals': 'locals',
'Locals##debug': 'Lokální proměnné',
'Log In': 'Přihlásit se',
'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně',
'Login': 'Přihlásit se',
'login': 'přihlásit se',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'logout': 'odhlásit se',
'Logout': 'Odhlásit se',
'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?',
'Manage': 'Spravovat',
'Manage Cache': 'Spravovat cache',
'Menu Model': 'Model rozbalovací nabídky',
'Models': 'Modely',
'models': 'modely',
'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy',
'Modules': 'Moduly',
'modules': 'moduly',
'My Sites': 'Správa aplikací',
'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena',
'New application wizard': 'Nový průvodce aplikací',
'New Application Wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo',
'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit novou aplikaci',
'next': 'další',
'next 100 rows': 'dalších 100 řádků',
'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo',
'Online book': 'Online kniha',
'online designer': 'online návrhář',
'Online examples': 'Ukázka aplikace: web2py stránky',
'Open new app in new window': 'Otevřít novou aplikaci v novém okně',
'or alternatively': 'nebo případně',
'Or Get from URL:': 'Nebo získat z URL adresy:',
'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad',
'Other Plugins': 'Ostatní moduly',
'Other Recipes': 'Ostatní zásuvné moduly',
'Overview': 'Přehled',
'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované',
'pack plugin': 'pack (zabalit) plugin',
'password': 'heslo',
'Password': 'Heslo',
"Password fields don't match": 'Hesla se neshodují',
'Peeking at file': 'Sledování souboru',
'Please': 'Prosím',
'Plugin "%s" in application': 'Plugin "%s" v aplikaci',
'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Množné číslo #%s',
'Plural-Forms:': 'Množná čísla:',
'Powered by': 'Používá technologii',
'Preface': 'Předmluva',
'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory',
'profile': 'profil',
'Project Progress': 'Vývoj projektu',
'Python': 'Python',
'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady',
'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána',
'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to',
'Record': 'Záznam',
'record does not exist': 'záznam neexistuje',
'Record ID': 'ID záznamu',
'Record id': 'id záznamu',
'refresh': 'obnovte',
'register': 'registrovat',
'Register': 'Zaregistrovat se',
'Registration identifier': 'Registrační identifikátor',
'Registration key': 'Registrační klíč',
'reload': 'reload',
'Reload routes': 'Znovu nahrát cesty',
'Remember me (for 30 days)': 'Zapamatovat na 30 dní',
'Remove compiled': 'Odstranit zkompilované',
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše',
'request': 'request',
'Reset Password key': 'Reset registračního klíče',
'response': 'response',
'restart': 'restart',
'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return',
'revert': 'vrátit se k původnímu',
'Role': 'Role',
'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s',
'Save': 'Uložit',
'Save file:': 'Uložit soubor:',
'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:',
'Semantic': 'Modul semantic',
'Services': 'Služby',
'session': 'session',
'session expired': 'vypršela session',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka',
'Sign Up': 'Registrovat se',
'Singular Form': 'Jednotné číslo',
'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:',
'skip to generate': 'přeskočit pro vytvoření',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce',
'state': 'stav',
'Static': 'Statické soubory',
'static': 'statické soubory',
'Static files': 'Statické soubory',
'Statistics': 'Statistika',
'Step': 'Krok',
'step': 'krok',
'stop': 'zastavit',
'Stylesheet': 'CSS styly',
'submit': 'odeslat',
'Submit': 'Odeslat',
'successful': 'úspěšně',
'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'Table': 'tabulka',
'Table name': 'Název tabulky',
'Temporary': 'Dočasný',
'test': 'test',
'Testing application': 'Zkušební aplikace',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Funkce vrátila dictionary (slovník) hodnot, a ty se vypsaly pomocí šablony %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The Views': 'Pohledy (The Views)',
'There are no controllers': 'Nejsou vytvořeny žádné controllery',
'There are no modules': 'Nejsou přidány žádné moduly',
'There are no plugins': 'Žádné pluginy nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'Nejsou přidány žádné statické soubory',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no views': 'Nejsou vytvořeny žádné šablony (views)',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is the %(filename)s template': 'Toto je šablona %(filename)s',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'Ticket': 'Tiket',
'Ticket ID': 'ID tiketu',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké',
'Traceback': 'Hierarchie volání',
'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'zkuste něco jako',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'vyzkoušet šablonu (view)',
'Twitter': 'Twitter',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'Nelze zjistit informaci o aktualizacích',
'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat',
'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky',
'Update:': 'Upravit:',
'Upgrade': 'Upgrade',
'upgrade now': 'upgradovat nyní',
'upgrade now to %s': 'upgradovat nyní na %s',
'upload': 'nahrát',
'Upload': 'Upload (nahrát)',
'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
'User %(id)s Profile updated': 'Uživatel %(id)s upravil profil',
'User %(id)s Registered': 'Uživatel %(id)s se zaregistroval',
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno',
'variables': 'proměnné',
'Verify Password': 'Zopakujte heslo',
'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování',
'Videos': 'Videa',
'View': 'Pohled (View)',
'Views': 'Pohledy',
'views': 'pohledy',
'Web Framework': 'Webový framework',
'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'aktualizace Web2py',
'web2py upgraded; please restart it': 'Web2py bylo aktualizováno; prosím restarujte jej',
'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve Web2py aplikaci',
'Welcome to web2py!': 'Vítejte ve Web2py aplikaci.',
'Which called the function %s located in the file %s': 'Tím byla zavolána funkce %s ze souboru (kontroléru) %s.',
'Working...': 'Pracuji...',
'You are successfully running web2py': 'Spustil(a) jsi webový server a Web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'You can modify this application and adapt it to your needs': 'V ADMIN rozhraní můžeš Vytvořit novou aplikaci jako kopii ukázkové Welcome aplikace. A začít upravovat: modely, kontroléry, šablony pro URL adresy, které požaduješ.',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You visited the url %s': 'Zadal jsi URL adresu %s.',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
}
+91 -55
View File
@@ -1,68 +1,100 @@
# -*- coding: utf-8 -*-
#########################################################################
## This scaffolding model makes your app work on Google App Engine too
## File is released under public domain and you can use without limitations
#########################################################################
# -------------------------------------------------------------------------
# This scaffolding model makes your app work on Google App Engine too
# File is released under public domain and you can use without limitations
# -------------------------------------------------------------------------
## if SSL/HTTPS is properly configured and you want all HTTP requests to
## be redirected to HTTPS, uncomment the line below:
if request.global_settings.web2py_version < "2.14.1":
raise HTTP(500, "Requires web2py 2.13.3 or newer")
# -------------------------------------------------------------------------
# if SSL/HTTPS is properly configured and you want all HTTP requests to
# be redirected to HTTPS, uncomment the line below:
# -------------------------------------------------------------------------
# request.requires_https()
## app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig
## once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
# once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
myconf = AppConfig(reload=True)
if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB
db = DAL(myconf.get('db.uri'),
pool_size = myconf.get('db.pool_size'),
migrate_enabled = myconf.get('db.migrate'),
check_reserved = ['all'])
# ---------------------------------------------------------------------
# if NOT running on Google App Engine use SQLite or other DB
# ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'),
pool_size=myconf.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'),
check_reserved=['all'])
else:
## connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
# connect to Google BigTable (optional 'google:datastore://namespace')
# ---------------------------------------------------------------------
db = DAL('google:datastore+ndb')
## store sessions and tickets there
# ---------------------------------------------------------------------
# store sessions and tickets there
# ---------------------------------------------------------------------
session.connect(request, response, db=db)
## or store session in Memcache, Redis, etc.
## from gluon.contrib.memdb import MEMDB
## from google.appengine.api.memcache import Client
## session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
# or store session in Memcache, Redis, etc.
# from gluon.contrib.memdb import MEMDB
# from google.appengine.api.memcache import Client
# session.connect(request, response, db = MEMDB(Client()))
# ---------------------------------------------------------------------
## by default give a view/generic.extension to all actions from localhost
## none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
# by default give a view/generic.extension to all actions from localhost
# none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
response.generic_patterns = ['*'] if request.is_local else []
## choose a style for forms
# -------------------------------------------------------------------------
# choose a style for forms
# -------------------------------------------------------------------------
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.get('forms.separator') or ''
## (optional) optimize handling of static files
# -------------------------------------------------------------------------
# (optional) optimize handling of static files
# -------------------------------------------------------------------------
# response.optimize_css = 'concat,minify,inline'
# response.optimize_js = 'concat,minify,inline'
## (optional) static assets folder versioning
# -------------------------------------------------------------------------
# (optional) static assets folder versioning
# -------------------------------------------------------------------------
# response.static_version = '0.0.0'
#########################################################################
## Here is sample code if you need for
## - email capabilities
## - authentication (registration, login, logout, ... )
## - authorization (role based authorization)
## - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
## - old style crud actions
## (more options discussed in gluon/tools.py)
#########################################################################
# -------------------------------------------------------------------------
# Here is sample code if you need for
# - email capabilities
# - authentication (registration, login, logout, ... )
# - authorization (role based authorization)
# - services (xml, csv, json, xmlrpc, jsonrpc, amf, rss)
# - old style crud actions
# (more options discussed in gluon/tools.py)
# -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager
auth = Auth(db, host=myconf.get('host.name'))
# host names must be a list of allowed host names (glob syntax allowed)
auth = Auth(db, host_names=myconf.get('host.names'))
service = Service()
plugins = PluginManager()
## create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
# create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
auth.define_tables(username=False, signature=False)
## configure email
# -------------------------------------------------------------------------
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender')
@@ -70,27 +102,31 @@ mail.settings.login = myconf.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False
## configure auth policy
# -------------------------------------------------------------------------
# configure auth policy
# -------------------------------------------------------------------------
auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
#########################################################################
## Define your tables below (or better in another model file) for example
##
## >>> db.define_table('mytable',Field('myfield','string'))
##
## Fields can be 'string','text','password','integer','double','boolean'
## 'date','time','datetime','blob','upload', 'reference TABLENAME'
## There is an implicit 'id integer autoincrement' field
## Consult manual for more options, validators, etc.
##
## More API examples for controllers:
##
## >>> db.mytable.insert(myfield='value')
## >>> rows=db(db.mytable.myfield=='value').select(db.mytable.ALL)
## >>> for row in rows: print row.id, row.myfield
#########################################################################
# -------------------------------------------------------------------------
# Define your tables below (or better in another model file) for example
#
# >>> db.define_table('mytable', Field('myfield', 'string'))
#
# Fields can be 'string','text','password','integer','double','boolean'
# 'date','time','datetime','blob','upload', 'reference TABLENAME'
# There is an implicit 'id integer autoincrement' field
# Consult manual for more options, validators, etc.
#
# More API examples for controllers:
#
# >>> db.mytable.insert(myfield='value')
# >>> rows = db(db.mytable.myfield == 'value').select(db.mytable.ALL)
# >>> for row in rows: print row.id, row.myfield
# -------------------------------------------------------------------------
## after defining tables, uncomment below to enable auditing
# -------------------------------------------------------------------------
# after defining tables, uncomment below to enable auditing
# -------------------------------------------------------------------------
# auth.enable_record_versioning(db)
+121 -108
View File
@@ -1,28 +1,32 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
#########################################################################
## Customize your APP title, subtitle and menus here
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# Customize your APP title, subtitle and menus here
# ----------------------------------------------------------------------------------------------------------------------
response.logo = A(B('web',SPAN(2),'py'),XML('&trade;&nbsp;'),
_class="navbar-brand",_href="http://www.web2py.com/",
response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'),
_class="navbar-brand", _href="http://www.web2py.com/",
_id="web2py-logo")
response.title = request.application.replace('_',' ').title()
response.title = request.application.replace('_', ' ').title()
response.subtitle = ''
## read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
response.meta.author = myconf.get('app.author')
response.meta.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
## your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
response.google_analytics_id = None
#########################################################################
## this is the main application menu add/remove items as required
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# this is the main application menu add/remove items as required
# ----------------------------------------------------------------------------------------------------------------------
response.menu = [
(T('Home'), False, URL('default', 'index'), [])
@@ -30,109 +34,118 @@ response.menu = [
DEVELOPMENT_MENU = True
#########################################################################
## provide shortcuts for development. remove in production
#########################################################################
# ----------------------------------------------------------------------------------------------------------------------
# provide shortcuts for development. remove in production
# ----------------------------------------------------------------------------------------------------------------------
def _():
# ------------------------------------------------------------------------------------------------------------------
# shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application
ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"),
(T('Controller'), False,
URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
(T('View'), False,
URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
(T('DB Model'), False,
URL(
'admin', 'default', 'edit/%s/models/db.py' % app)),
(T('Menu Model'), False,
URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)),
(T('Config.ini'), False,
URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
(T('Layout'), False,
URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)),
(T('Stylesheet'), False,
URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')),
(T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)),
(T('About'), False, URL(
'admin', 'default', 'about/' + app)),
]),
('web2py.com', False, '#', [
(T('Download'), False,
'http://www.web2py.com/examples/default/download'),
(T('Support'), False,
'http://www.web2py.com/examples/default/support'),
(T('Demo'), False, 'http://web2py.com/demo_admin'),
(T('Quick Examples'), False,
'http://web2py.com/examples/default/examples'),
(T('FAQ'), False, 'http://web2py.com/AlterEgo'),
(T('Videos'), False,
'http://www.web2py.com/examples/default/videos/'),
(T('Free Applications'),
False, 'http://web2py.com/appliances'),
(T('Plugins'), False, 'http://web2py.com/plugins'),
(T('Recipes'), False, 'http://web2pyslices.com/'),
]),
(T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"),
(T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False,
'http://www.web2py.com/book/default/chapter/01'),
(T('Python'), False,
'http://www.web2py.com/book/default/chapter/02'),
(T('Overview'), False,
'http://www.web2py.com/book/default/chapter/03'),
(T('The Core'), False,
'http://www.web2py.com/book/default/chapter/04'),
(T('The Views'), False,
'http://www.web2py.com/book/default/chapter/05'),
(T('Database'), False,
'http://www.web2py.com/book/default/chapter/06'),
(T('Forms and Validators'), False,
'http://www.web2py.com/book/default/chapter/07'),
(T('Email and SMS'), False,
'http://www.web2py.com/book/default/chapter/08'),
(T('Access Control'), False,
'http://www.web2py.com/book/default/chapter/09'),
(T('Services'), False,
'http://www.web2py.com/book/default/chapter/10'),
(T('Ajax Recipes'), False,
'http://www.web2py.com/book/default/chapter/11'),
(T('Components and Plugins'), False,
'http://www.web2py.com/book/default/chapter/12'),
(T('Deployment Recipes'), False,
'http://www.web2py.com/book/default/chapter/13'),
(T('Other Recipes'), False,
'http://www.web2py.com/book/default/chapter/14'),
(T('Helping web2py'), False,
'http://www.web2py.com/book/default/chapter/15'),
(T("Buy web2py's book"), False,
'http://stores.lulu.com/web2py'),
]),
(T('Community'), False, None, [
(T('Groups'), False,
'http://www.web2py.com/examples/default/usergroups'),
(T('Twitter'), False, 'http://twitter.com/web2py'),
(T('Live Chat'), False,
'http://webchat.freenode.net/?channels=web2py'),
]),
]
if DEVELOPMENT_MENU: _()
(T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"),
(T('Controller'), False,
URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
(T('View'), False,
URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
(T('DB Model'), False,
URL(
'admin', 'default', 'edit/%s/models/db.py' % app)),
(T('Menu Model'), False,
URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)),
(T('Config.ini'), False,
URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
(T('Layout'), False,
URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)),
(T('Stylesheet'), False,
URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')),
(T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)),
(T('About'), False, URL(
'admin', 'default', 'about/' + app)),
]),
('web2py.com', False, '#', [
(T('Download'), False,
'http://www.web2py.com/examples/default/download'),
(T('Support'), False,
'http://www.web2py.com/examples/default/support'),
(T('Demo'), False, 'http://web2py.com/demo_admin'),
(T('Quick Examples'), False,
'http://web2py.com/examples/default/examples'),
(T('FAQ'), False, 'http://web2py.com/AlterEgo'),
(T('Videos'), False,
'http://www.web2py.com/examples/default/videos/'),
(T('Free Applications'),
False, 'http://web2py.com/appliances'),
(T('Plugins'), False, 'http://web2py.com/plugins'),
(T('Recipes'), False, 'http://web2pyslices.com/'),
]),
(T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"),
(T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False,
'http://www.web2py.com/book/default/chapter/01'),
(T('Python'), False,
'http://www.web2py.com/book/default/chapter/02'),
(T('Overview'), False,
'http://www.web2py.com/book/default/chapter/03'),
(T('The Core'), False,
'http://www.web2py.com/book/default/chapter/04'),
(T('The Views'), False,
'http://www.web2py.com/book/default/chapter/05'),
(T('Database'), False,
'http://www.web2py.com/book/default/chapter/06'),
(T('Forms and Validators'), False,
'http://www.web2py.com/book/default/chapter/07'),
(T('Email and SMS'), False,
'http://www.web2py.com/book/default/chapter/08'),
(T('Access Control'), False,
'http://www.web2py.com/book/default/chapter/09'),
(T('Services'), False,
'http://www.web2py.com/book/default/chapter/10'),
(T('Ajax Recipes'), False,
'http://www.web2py.com/book/default/chapter/11'),
(T('Components and Plugins'), False,
'http://www.web2py.com/book/default/chapter/12'),
(T('Deployment Recipes'), False,
'http://www.web2py.com/book/default/chapter/13'),
(T('Other Recipes'), False,
'http://www.web2py.com/book/default/chapter/14'),
(T('Helping web2py'), False,
'http://www.web2py.com/book/default/chapter/15'),
(T("Buy web2py's book"), False,
'http://stores.lulu.com/web2py'),
]),
(T('Community'), False, None, [
(T('Groups'), False,
'http://www.web2py.com/examples/default/usergroups'),
(T('Twitter'), False, 'http://twitter.com/web2py'),
(T('Live Chat'), False,
'http://webchat.freenode.net/?channels=web2py'),
]),
]
if "auth" in locals(): auth.wikimenu()
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()
+1 -1
View File
@@ -8,7 +8,7 @@ generator = Web2py Web Framework
; Host configuration
[host]
name = localhost
names = localhost:*, 127.0.0.1:*, *:*, *
; db configuration
[db]
+15 -12
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------------------------------------------------
# This is an app-specific example router
#
# This simple router is used for setting languages from app/languages directory
@@ -8,31 +9,33 @@
# a default_language
#
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
#-------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------------
# To enable this route file you must do the steps:
#
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
# 2. rename this APP/routes.example.py to APP/routes.py
# (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interfase)
# 2. rename this APP/routes.example.py to APP/routes.py (where APP - is your application directory)
# 3. restart web2py (or reload routes in web2py admin interface)
#
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath
from languages import read_possible_languages
possible_languages = read_possible_languages(abspath('applications', app))
#NOTE! app - is an application based router's parameter with name of an
# application. E.g.'welcome'
# ----------------------------------------------------------------------------------------------------------------------
# NOTE! app - is an application based router's parameter with name of an application. E.g.'welcome'
# ----------------------------------------------------------------------------------------------------------------------
routers = {
app: dict(
default_language = possible_languages['default'][0],
languages = [lang for lang in possible_languages
if lang != 'default']
default_language=possible_languages['default'][0],
languages=[lang for lang in possible_languages if lang != 'default']
)
}
#NOTE! To change language in your application using these rules add this line
#in one of your models files:
# ----------------------------------------------------------------------------------------------------------------------
# NOTE! To change language in your application using these rules add this line in one of your models files:
# ----------------------------------------------------------------------------------------------------------------------
# if request.uri_language: T.force(request.uri_language)
File diff suppressed because one or more lines are too long
Vendored
+3 -2
View File
@@ -115,8 +115,9 @@ def deploy(appname=None, all=False):
"""fab -H username@host deploy:appname,all"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
if os.path.exists('_update.zip'):
os.unlink('_update.zip')
zipfile = os.path.join(appfolder, '_update.zip')
if os.path.exists(zipfile):
os.unlink(zipfile)
backup = mkdir_or_backup(appname)
+3 -3
View File
@@ -148,7 +148,7 @@ def app_compile(app, request, skip_failed_views=False):
failed_views = compile_application(folder, skip_failed_views)
return failed_views
except (Exception, RestrictedError):
tb = traceback.format_exc(sys.exc_info)
tb = traceback.format_exc()
remove_compiled_application(folder)
return tb
@@ -167,7 +167,7 @@ def app_create(app, request, force=False, key=None, info=False):
os.mkdir(path)
except:
if info:
return False, traceback.format_exc(sys.exc_info)
return False, traceback.format_exc()
else:
return False
elif not force:
@@ -197,7 +197,7 @@ def app_create(app, request, force=False, key=None, info=False):
except:
rmtree(path)
if info:
return False, traceback.format_exc(sys.exc_info)
return False, traceback.format_exc()
else:
return False
+37 -50
View File
@@ -44,9 +44,9 @@ except ImportError:
have_settings = False
try:
import cPickle as pickle
import cPickle as pickle
except:
import pickle
import pickle
try:
import psutil
@@ -54,6 +54,7 @@ try:
except ImportError:
HAVE_PSUTIL = False
def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%)
old_mem = psutil.virtual_memory().percent
@@ -66,7 +67,8 @@ def remove_oldest_entries(storage, percentage=90):
# comute used memory again
new_mem = psutil.virtual_memory().percent
# if the used memory did not decrease stop
if new_mem >= old_mem: break
if new_mem >= old_mem:
break
# net new measurement for memory usage and loop
old_mem = new_mem
@@ -78,6 +80,7 @@ __all__ = ['Cache', 'lazy_cache']
DEFAULT_TIME_EXPIRE = 300
class CacheAbstract(object):
"""
Abstract class for cache implementations.
@@ -99,7 +102,7 @@ class CacheAbstract(object):
"""
cache_stats_name = 'web2py_cache_statistics'
max_ram_utilization = None # percent
max_ram_utilization = None # percent
def __init__(self, request=None):
"""Initializes the object
@@ -182,13 +185,14 @@ class CacheInRam(CacheAbstract):
self.request = request
self.storage = OrderedDict() if HAVE_PSUTIL else {}
self.app = request.application if request else ''
def initialize(self):
if self.initialized:
return
else:
self.initialized = True
self.locker.acquire()
if not self.app in self.meta_storage:
if self.app not in self.meta_storage:
self.storage = self.meta_storage[self.app] = \
OrderedDict() if HAVE_PSUTIL else {}
self.stats[self.app] = {'hit_total': 0, 'misses': 0}
@@ -205,7 +209,7 @@ class CacheInRam(CacheAbstract):
else:
self._clear(storage, regex)
if not self.app in self.stats:
if self.app not in self.stats:
self.stats[self.app] = {'hit_total': 0, 'misses': 0}
self.locker.release()
@@ -251,8 +255,8 @@ class CacheInRam(CacheAbstract):
self.locker.acquire()
self.storage[key] = (now, value)
self.stats[self.app]['misses'] += 1
if HAVE_PSUTIL and self.max_ram_utilization!=None and random.random()<0.10:
remove_oldest_entries(self.storage, percentage = self.max_ram_utilization)
if HAVE_PSUTIL and self.max_ram_utilization is not None and random.random() < 0.10:
remove_oldest_entries(self.storage, percentage=self.max_ram_utilization)
self.locker.release()
return value
@@ -292,14 +296,15 @@ class CacheOnDisk(CacheAbstract):
self.folder = folder
self.key_filter_in = lambda key: key
self.key_filter_out = lambda key: key
self.file_lock_time_wait = file_lock_time_wait # How long we should wait before retrying to lock a file held by another process
self.file_lock_time_wait = file_lock_time_wait
# How long we should wait before retrying to lock a file held by another process
# We still need a mutex for each file as portalocker only blocks other processes
self.file_locks = defaultdict(thread.allocate_lock)
# Make sure we use valid filenames.
if sys.platform == "win32":
import base64
def key_filter_in_windows(key):
"""
Windows doesn't allow \ / : * ? "< > | in filenames.
@@ -316,7 +321,6 @@ class CacheOnDisk(CacheAbstract):
self.key_filter_in = key_filter_in_windows
self.key_filter_out = key_filter_out_windows
def wait_portalock(self, val_file):
"""
Wait for the process file lock.
@@ -328,15 +332,12 @@ class CacheOnDisk(CacheAbstract):
except:
time.sleep(self.file_lock_time_wait)
def acquire(self, key):
self.file_locks[key].acquire()
def release(self, key):
self.file_locks[key].release()
def __setitem__(self, key, value):
key = self.key_filter_in(key)
val_file = recfile.open(key, mode='wb', path=self.folder)
@@ -344,7 +345,6 @@ class CacheOnDisk(CacheAbstract):
pickle.dump(value, val_file, pickle.HIGHEST_PROTOCOL)
val_file.close()
def __getitem__(self, key):
key = self.key_filter_in(key)
try:
@@ -357,12 +357,10 @@ class CacheOnDisk(CacheAbstract):
val_file.close()
return value
def __contains__(self, key):
key = self.key_filter_in(key)
return (key in self.file_locks) or recfile.exists(key, path=self.folder)
def __delitem__(self, key):
key = self.key_filter_in(key)
try:
@@ -370,13 +368,11 @@ class CacheOnDisk(CacheAbstract):
except IOError:
raise KeyError
def __iter__(self):
for dirpath, dirnames, filenames in os.walk(self.folder):
for filename in filenames:
yield self.key_filter_out(filename)
def safe_apply(self, key, function, default_value=None):
"""
Safely apply a function to the value of a key in storage and set
@@ -403,25 +399,21 @@ class CacheOnDisk(CacheAbstract):
val_file.close()
return new_value
def keys(self):
return list(self.__iter__())
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __init__(self, request=None, folder=None):
self.initialized = False
self.request = request
self.folder = folder
self.storage = None
def initialize(self):
if self.initialized:
return
@@ -440,7 +432,6 @@ class CacheOnDisk(CacheAbstract):
self.storage = CacheOnDisk.PersistentStorage(folder)
def __call__(self, key, f,
time_expire=DEFAULT_TIME_EXPIRE):
self.initialize()
@@ -487,7 +478,6 @@ class CacheOnDisk(CacheAbstract):
self.storage.release(key)
return value
def clear(self, regex=None):
self.initialize()
storage = self.storage
@@ -504,7 +494,6 @@ class CacheOnDisk(CacheAbstract):
pass
storage.release(key)
def increment(self, key, value=1):
self.initialize()
self.storage.acquire(key)
@@ -513,7 +502,6 @@ class CacheOnDisk(CacheAbstract):
return value
class CacheAction(object):
def __init__(self, func, key, time_expire, cache, cache_model):
self.__name__ = func.__name__
@@ -572,9 +560,9 @@ class Cache(object):
logger.warning('no cache.disk (AttributeError)')
def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None,
quick=None):
prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None,
quick=None):
"""Better fit for caching an action
Warning:
@@ -602,6 +590,7 @@ class Cache(object):
"""
from gluon import current
from gluon.http import HTTP
def wrap(func):
def wrapped_f():
if current.request.env.request_method != 'GET':
@@ -621,13 +610,14 @@ class Cache(object):
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
if not session_ and public_:
cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)
).strftime('%a, %d %b %Y %H:%M:%S GMT')
else:
cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model:
#figure out the correct cache key
# figure out the correct cache key
cache_key = [current.request.env.path_info, current.response.view]
if session_:
cache_key.append(current.response.session_id)
@@ -644,28 +634,28 @@ class Cache(object):
if prefix:
cache_key = prefix + cache_key
try:
#action returns something
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
# action returns something
rtn = cache_model(cache_key, lambda: func(), time_expire=time_expire)
http, status = None, current.response.status
except HTTP, e:
#action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda : e.body, time_expire=time_expire)
# action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda: e.body, time_expire=time_expire)
http, status = HTTP(e.status, rtn, **e.headers), e.status
else:
#action raised a generic exception
# action raised a generic exception
http = None
else:
#no server-cache side involved
# no server-cache side involved
try:
#action returns something
# action returns something
rtn = func()
http, status = None, current.response.status
except HTTP, e:
#action raises HTTP (can still be valid)
# action raises HTTP (can still be valid)
status = e.status
http = HTTP(e.status, e.body, **e.headers)
else:
#action raised a generic exception
# action raised a generic exception
http = None
send_headers = False
if http and isinstance(valid_statuses, list):
@@ -675,15 +665,13 @@ class Cache(object):
if str(status)[0] in '123':
send_headers = True
if send_headers:
headers = {
'Pragma' : None,
'Expires' : expires,
'Cache-Control' : cache_control
}
headers = {'Pragma': None,
'Expires': expires,
'Cache-Control': cache_control}
current.response.headers.update(headers)
if cache_model and not send_headers:
#we cached already the value, but the status is not valid
#so we need to delete the cached value
# we cached already the value, but the status is not valid
# so we need to delete the cached value
cache_model(cache_key, None)
if http:
if send_headers:
@@ -740,8 +728,7 @@ class Cache(object):
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)
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'):
+2 -2
View File
@@ -676,8 +676,8 @@ def run_view_in(environment):
else:
filename = pjoin(folder, 'views', view)
if os.path.exists(path): # compiled views
x = view.replace('/', '.')
files = ['views.%s.pyc' % x]
x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
is_compiled = os.path.exists(pjoin(path, files[0]))
# Don't use a generic view if the non-compiled view exists.
if is_compiled or (not is_compiled and not os.path.exists(filename)):
+2 -2
View File
@@ -69,8 +69,8 @@ class AppConfigDict(dict):
return False
elif value.isdigit() or (value[0]=='-' and value[1:].isdigit()):
return int(value)
elif ', ' in value:
return value.split(', ')
elif ',' in value:
return map(lambda x:x.strip(),value.split(','))
else:
try:
return float(value)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -27,7 +27,7 @@ from gluon import current
class RESIZE(object):
def __init__(self, nx=160, ny=80, quality=100, padding = False
def __init__(self, nx=160, ny=80, quality=100, padding = False,
error_message=' image resize'):
(self.nx, self.ny, self.quality, self.error_message, self.padding) = (
nx, ny, quality, error_message, padding)
+22 -2
View File
@@ -36,11 +36,13 @@ def ldap_auth(server='ldap',
user_lastname_attrib='cn:2',
user_mail_attrib='mail',
manage_groups=False,
manage_groups_callback=[],
db=None,
group_dn=None,
group_name_attrib='cn',
group_member_attrib='memberUid',
group_filterstr='objectClass=*',
group_mapping={},
tls=False,
logging_level='error'):
@@ -207,6 +209,7 @@ def ldap_auth(server='ldap',
user_mail_attrib=user_mail_attrib,
manage_groups=manage_groups,
allowed_groups=allowed_groups,
group_mapping=group_mapping,
db=db):
if password == '': # http://tools.ietf.org/html/rfc4513#section-5.1.2
logger.warning('blank password not allowed')
@@ -262,6 +265,7 @@ def ldap_auth(server='ldap',
requested_attrs = ['sAMAccountName']
if manage_user:
requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
result = con.search_ext_s(
ldap_basedn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr),
@@ -444,7 +448,7 @@ def ldap_auth(server='ldap',
con.unbind()
if manage_groups:
if not do_manage_groups(username, password):
if not do_manage_groups(username, password, group_mapping):
return False
return True
except ldap.INVALID_CREDENTIALS, e:
@@ -482,7 +486,7 @@ def ldap_auth(server='ldap',
# No match
return False
def do_manage_groups(username, password=None, db=db):
def do_manage_groups(username, password=None, group_mapping={}, db=db):
"""
Manage user groups
@@ -498,6 +502,14 @@ def ldap_auth(server='ldap',
ldap_groups_of_the_user = get_user_groups_from_ldap(
username, password)
if group_mapping != {}:
l = []
for group in ldap_groups_of_the_user:
if group in group_mapping:
l += group_mapping[group]
ldap_groups_of_the_user = l
logging.info("User groups after remapping: %s" % str(l))
#
# Get all group name where the user is in actually in local db
# #############################################################
@@ -540,6 +552,7 @@ def ldap_auth(server='ldap',
db_groups_of_the_user.append(group.role)
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
auth_membership_changed = False
#
# Delete user membership from groups where user is not anymore
# #############################################################
@@ -547,6 +560,7 @@ def ldap_auth(server='ldap',
if ldap_groups_of_the_user.count(group_to_del) == 0:
db((db.auth_membership.user_id == db_user_id) &
(db.auth_membership.group_id == db_group_id[group_to_del])).delete()
auth_membership_changed = True
#
# Create user membership in groups where user is not in already
@@ -558,6 +572,12 @@ def ldap_auth(server='ldap',
else:
gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id
db.auth_membership.insert(user_id=db_user_id, group_id=gid)
auth_membership_changed = True
if auth_membership_changed:
for callback in manage_groups_callback:
callback()
except:
logger.warning("[%s] Groups are not managed successfully!" % str(username))
import traceback
@@ -51,7 +51,7 @@ class OneallAccount(object):
reg_id=profile.get('identity_token','')
username=profile.get('preferredUsername',email)
first_name=name.get('givenName', dname.split(' ')[0])
last_name=profile.get('familyName', dname.split(' ')[1] if(len(dname.split(' ')) > 1) else None)
last_name=profile.get('familyName', dname.split(' ')[1] if(dname.count(' ') > 0) else None)
return dict(registration_id=reg_id,username=username,email=email,
first_name=first_name,last_name=last_name)
self.mappings.default = defaultmapping
+117 -24
View File
@@ -53,8 +53,9 @@ see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
* header-ids: Adds "id" attributes to headers. The id value is a slug of
the header text.
* html-classes: Takes a dict mapping html tag names (lowercase) to a
string to use for a "class" tag attribute. Currently only supports
"pre" and "code" tags. Add an issue if you require this for other tags.
string to use for a "class" tag attribute. Currently only supports "img",
"table", "pre" and "code" tags. Add an issue if you require this for other
tags.
* markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to
have markdown processing be done on its contents. Similar to
<http://michelf.com/projects/php-markdown/extra/#markdown-attr> but with
@@ -70,9 +71,14 @@ see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
* smarty-pants: Replaces ' and " with curly quotation marks or curly
apostrophes. Replaces --, ---, ..., and . . . with en dashes, em dashes,
and ellipses.
* spoiler: A special kind of blockquote commonly hidden behind a
click on SO. Syntax per <http://meta.stackexchange.com/a/72878>.
* toc: The returned HTML string gets a new "toc_html" attribute which is
a Table of Contents for the document. (experimental)
* xml: Passes one-liner processing instructions and namespaced XML tags.
* tables: Tables using the same format as GFM
<https://help.github.com/articles/github-flavored-markdown#tables> and
PHP-Markdown Extra <https://michelf.ca/projects/php-markdown/extra/#table>.
* wiki-tables: Google Code Wiki-style tables. See
<http://code.google.com/p/support/wiki/WikiSyntax#Tables>.
"""
@@ -82,13 +88,11 @@ see <https://github.com/trentm/python-markdown2/wiki/Extras> for details):
# not yet sure if there implications with this. Compare 'pydoc sre'
# and 'perldoc perlre'.
__version_info__ = (2, 2, 4)
__version_info__ = (2, 3, 1)
__version__ = '.'.join(map(str, __version_info__))
__author__ = "Trent Mick"
import os
import sys
from pprint import pprint
import re
import logging
try:
@@ -102,13 +106,7 @@ import codecs
#---- Python version compat
try:
from urllib.parse import quote # python3
except ImportError:
from urllib import quote # python2
if sys.version_info[:2] < (2,4):
from sets import Set as set
def reversed(sequence):
for i in sequence[::-1]:
yield i
@@ -804,6 +802,8 @@ class Markdown(object):
text = self._prepare_pyshell_blocks(text)
if "wiki-tables" in self.extras:
text = self._do_wiki_tables(text)
if "tables" in self.extras:
text = self._do_tables(text)
text = self._do_code_blocks(text)
@@ -844,6 +844,79 @@ class Markdown(object):
return _pyshell_block_re.sub(self._pyshell_block_sub, text)
def _table_sub(self, match):
trim_space_re = '^[ \t\n]+|[ \t\n]+$'
trim_bar_re = '^\||\|$'
head, underline, body = match.groups()
# Determine aligns for columns.
cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", underline)).split('|')]
align_from_col_idx = {}
for col_idx, col in enumerate(cols):
if col[0] == ':' and col[-1] == ':':
align_from_col_idx[col_idx] = ' align="center"'
elif col[0] == ':':
align_from_col_idx[col_idx] = ' align="left"'
elif col[-1] == ':':
align_from_col_idx[col_idx] = ' align="right"'
# thead
hlines = ['<table%s>' % self._html_class_str_from_tag('table'), '<thead>', '<tr>']
cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", head)).split('|')]
for col_idx, col in enumerate(cols):
hlines.append(' <th%s>%s</th>' % (
align_from_col_idx.get(col_idx, ''),
self._run_span_gamut(col)
))
hlines.append('</tr>')
hlines.append('</thead>')
# tbody
hlines.append('<tbody>')
for line in body.strip('\n').split('\n'):
hlines.append('<tr>')
cols = [cell.strip() for cell in re.sub(trim_bar_re, "", re.sub(trim_space_re, "", line)).split('|')]
for col_idx, col in enumerate(cols):
hlines.append(' <td%s>%s</td>' % (
align_from_col_idx.get(col_idx, ''),
self._run_span_gamut(col)
))
hlines.append('</tr>')
hlines.append('</tbody>')
hlines.append('</table>')
return '\n'.join(hlines) + '\n'
def _do_tables(self, text):
"""Copying PHP-Markdown and GFM table syntax. Some regex borrowed from
https://github.com/michelf/php-markdown/blob/lib/Michelf/Markdown.php#L2538
"""
less_than_tab = self.tab_width - 1
table_re = re.compile(r'''
(?:(?<=\n\n)|\A\n?) # leading blank line
^[ ]{0,%d} # allowed whitespace
(.*[|].*) \n # $1: header row (at least one pipe)
^[ ]{0,%d} # allowed whitespace
( # $2: underline row
# underline row with leading bar
(?: \|\ *:?-+:?\ * )+ \|? \n
|
# or, underline row without leading bar
(?: \ *:?-+:?\ *\| )+ (?: \ *:?-+:?\ * )? \n
)
( # $3: data rows
(?:
^[ ]{0,%d}(?!\ ) # ensure line begins with 0 to less_than_tab spaces
.*\|.* \n
)+
)
''' % (less_than_tab, less_than_tab, less_than_tab), re.M | re.X)
return table_re.sub(self._table_sub, text)
def _wiki_table_sub(self, match):
ttext = match.group(0).strip()
#print 'wiki table: %r' % match.group(0)
@@ -853,7 +926,7 @@ class Markdown(object):
row = [c.strip() for c in re.split(r'(?<!\\)\|\|', line)]
rows.append(row)
#pprint(rows)
hlines = ['<table>', '<tbody>']
hlines = ['<table%s>' % self._html_class_str_from_tag('table'), '<tbody>']
for row in rows:
hrow = ['<tr>']
for cell in row:
@@ -899,6 +972,9 @@ class Markdown(object):
text = self._encode_amps_and_angles(text)
if "strike" in self.extras:
text = self._do_strike(text)
text = self._do_italics_and_bold(text)
if "smarty-pants" in self.extras:
@@ -1206,7 +1282,6 @@ class Markdown(object):
.replace('_', self._escape_table['_'])
title = self.titles.get(link_id)
if title:
before = title
title = _xml_escape_attr(title) \
.replace('*', self._escape_table['*']) \
.replace('_', self._escape_table['_'])
@@ -1418,7 +1493,6 @@ class Markdown(object):
def _list_item_sub(self, match):
item = match.group(4)
leading_line = match.group(1)
leading_space = match.group(2)
if leading_line or "\n\n" in item or self._last_li_endswith_two_eols:
item = self._run_block_gamut(self._outdent(item))
else:
@@ -1654,6 +1728,11 @@ class Markdown(object):
self._escape_table[text] = hashed
return hashed
_strike_re = re.compile(r"~~(?=\S)(.+?)(?<=\S)~~", re.S)
def _do_strike(self, text):
text = self._strike_re.sub(r"<strike>\1</strike>", text)
return text
_strong_re = re.compile(r"(\*\*|__)(?=\S)(.+?[*_]*)(?<=\S)\1", re.S)
_em_re = re.compile(r"(\*|_)(?=\S)(.+?)(?<=\S)\1", re.S)
_code_friendly_strong_re = re.compile(r"\*\*(?=\S)(.+?[*_]*)(?<=\S)\*\*", re.S)
@@ -1714,38 +1793,53 @@ class Markdown(object):
text = text.replace(". . .", "&#8230;")
return text
_block_quote_re = re.compile(r'''
_block_quote_base = r'''
( # Wrap whole match in \1
(
^[ \t]*>[ \t]? # '>' at the start of a line
^[ \t]*>%s[ \t]? # '>' at the start of a line
.+\n # rest of the first line
(.+\n)* # subsequent consecutive lines
\n* # blanks
)+
)
''', re.M | re.X)
'''
_block_quote_re = re.compile(_block_quote_base % '', re.M | re.X)
_block_quote_re_spoiler = re.compile(_block_quote_base % '[ \t]*?!?', re.M | re.X)
_bq_one_level_re = re.compile('^[ \t]*>[ \t]?', re.M);
_bq_one_level_re_spoiler = re.compile('^[ \t]*>[ \t]*?![ \t]?', re.M);
_bq_all_lines_spoilers = re.compile(r'\A(?:^[ \t]*>[ \t]*?!.*[\n\r]*)+\Z', re.M)
_html_pre_block_re = re.compile(r'(\s*<pre>.+?</pre>)', re.S)
def _dedent_two_spaces_sub(self, match):
return re.sub(r'(?m)^ ', '', match.group(1))
def _block_quote_sub(self, match):
bq = match.group(1)
bq = self._bq_one_level_re.sub('', bq) # trim one level of quoting
bq = self._ws_only_line_re.sub('', bq) # trim whitespace-only lines
is_spoiler = 'spoiler' in self.extras and self._bq_all_lines_spoilers.match(bq)
# trim one level of quoting
if is_spoiler:
bq = self._bq_one_level_re_spoiler.sub('', bq)
else:
bq = self._bq_one_level_re.sub('', bq)
# trim whitespace-only lines
bq = self._ws_only_line_re.sub('', bq)
bq = self._run_block_gamut(bq) # recurse
bq = re.sub('(?m)^', ' ', bq)
# These leading spaces screw with <pre> content, so we need to fix that:
bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
return "<blockquote>\n%s\n</blockquote>\n\n" % bq
if is_spoiler:
return '<blockquote class="spoiler">\n%s\n</blockquote>\n\n' % bq
else:
return '<blockquote>\n%s\n</blockquote>\n\n' % bq
def _do_block_quotes(self, text):
if '>' not in text:
return text
return self._block_quote_re.sub(self._block_quote_sub, text)
if 'spoiler' in self.extras:
return self._block_quote_re_spoiler.sub(self._block_quote_sub, text)
else:
return self._block_quote_re.sub(self._block_quote_sub, text)
def _form_paragraphs(self, text):
# Strip leading and trailing lines:
@@ -2053,7 +2147,6 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False):
if DEBUG:
print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
% (tabsize, skip_first_line))
indents = []
margin = None
for i, line in enumerate(lines):
if i == 0 and skip_first_line: continue
@@ -2362,4 +2455,4 @@ def main(argv=None):
if __name__ == "__main__":
sys.exit( main(sys.argv) )
sys.exit( main(sys.argv) )
+288 -256
View File
@@ -7,10 +7,11 @@ import re
import urllib
from cgi import escape
from string import maketrans
try:
from ast import parse as ast_parse
import ast
except ImportError: # python 2.5
from ast import parse as ast_parse
import ast
except ImportError: # python 2.5
from compiler import parse
import compiler.ast as ast
@@ -530,41 +531,47 @@ As shown in Ref.!`!`mdipierro`!`!:cite
``<ul/>``, ``<ol/>``, ``<code/>``, ``<table/>``, ``<blockquote/>``, ``<h1/>``, ..., ``<h6/>`` do not have ``<p>...</p>`` around them.
"""
html_colors=['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
'silver', 'teal', 'white', 'yellow']
html_colors = ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green',
'lime', 'maroon', 'navy', 'olive', 'purple', 'red',
'silver', 'teal', 'white', 'yellow']
META = '\x06'
LINK = '\x07'
DISABLED_META = '\x08'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
regex_URL=re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env2=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+'|````)')
regex_dd=re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
regex_strong=re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_del=re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_em=re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_URL = re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env2 = re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('(' + META + '|' + DISABLED_META + '|````)')
regex_dd = re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile(
'(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',
re.S)
regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
regex_del = re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
regex_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
regex_list = re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_bq_headline = re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq = re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)',re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w|[\w\-.]+@[\w\-.]+)', re.M)
regex_link = re.compile(r'(' + LINK + r')|\[\[(?P<s>.+?)\]\]', re.S)
regex_link_level2 = re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$', re.S)
regex_media_level2 = re.compile(
r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',
re.S)
regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05')
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05',"'`:*~\\[]{}@$+-.#\n")
ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05')
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':",b)
return '{%s}' % regex_quote.sub("'\g<name>':", b)
def safe_eval(node_or_string, env):
"""
Safely evaluate an expression node or a string containing a Python
@@ -578,6 +585,7 @@ def safe_eval(node_or_string, env):
node_or_string = ast_parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
def _convert(node):
if isinstance(node, ast.Str):
return node.s
@@ -594,11 +602,11 @@ def safe_eval(node_or_string, env):
if node.id in _safe_names:
return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)):
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)):
left = node.left.n
right = node.right.n
if isinstance(node.op, Add):
@@ -606,57 +614,66 @@ def safe_eval(node_or_string, env):
else:
return left - right
raise ValueError('malformed string')
return _convert(node_or_string)
def markmin_escape(text):
""" insert \\ before markmin control characters: '`:*~[]{}@$ """
return regex_markmin_escape.sub(
lambda m: '\\'+m.group(0).replace('\\','\\\\'), text)
lambda m: '\\' + m.group(0).replace('\\', '\\\\'), text)
def replace_autolinks(text,autolinks):
def replace_autolinks(text, autolinks):
return regex_auto.sub(lambda m: autolinks(m.group('k')), text)
def replace_at_urls(text,url):
# this is experimental @{function/args}
def u1(match,url=url):
a,c,f,args = match.group('a','c','f','args')
return url(a=a or None,c=c or None,f = f or None,
args=(args or '').split('/'), scheme=True, host=True)
return regex_URL.sub(u1,text)
def replace_components(text,env):
def replace_at_urls(text, url):
# this is experimental @{function/args}
def u1(match, url=url):
a, c, f, args = match.group('a', 'c', 'f', 'args')
return url(a=a or None, c=c or None, f=f or None,
args=(args or '').split('/'), scheme=True, host=True)
return regex_URL.sub(u1, text)
def replace_components(text, env):
# not perfect but acceptable
def u2(match, env=env):
f = env.get(match.group('a'), match.group(0))
if callable(f):
b = match.group('b')
try:
b = safe_eval(make_dict(b),env)
b = safe_eval(make_dict(b), env)
except:
pass
try:
f = f(**b) if isinstance(b,dict) else f(b)
f = f(**b) if isinstance(b, dict) else f(b)
except Exception, e:
f = 'ERROR: %s' % e
return str(f)
text = regex_env2.sub(u2, text)
return text
def autolinks_simple(url):
"""
it automatically converts the url to link,
image, video or audio tag
"""
u_url=url.lower()
if '@' in url and not '://' in url:
u_url = url.lower()
if '@' in url and '://' not in url:
return '<a href="mailto:%s">%s</a>' % (url, url)
elif u_url.endswith(('.jpg','.jpeg','.gif','.png')):
elif u_url.endswith(('.jpg', '.jpeg', '.gif', '.png')):
return '<img src="%s" controls />' % url
elif u_url.endswith(('.mp4','.mpeg','.mov','.ogv')):
elif u_url.endswith(('.mp4', '.mpeg', '.mov', '.ogv')):
return '<video src="%s" controls></video>' % url
elif u_url.endswith(('.mp3','.wav','.ogg')):
elif u_url.endswith(('.mp3', '.wav', '.ogg')):
return '<audio src="%s" controls></audio>' % url
return '<a href="%s">%s</a>' % (url,url)
return '<a href="%s">%s</a>' % (url, url)
def protolinks_simple(proto, url):
"""
@@ -667,16 +684,18 @@ def protolinks_simple(proto, url):
proto="iframe"
url="http://www.example.com/path"
"""
if proto in ('iframe','embed'): #== 'iframe':
return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>'%url
#elif proto == 'embed': # NOTE: embed is a synonym to iframe now
if proto in ('iframe', 'embed'): # == 'iframe':
return '<iframe src="%s" frameborder="0" allowfullscreen></iframe>' % url
# elif proto == 'embed': # NOTE: embed is a synonym to iframe now
# return '<a href="%s" class="%sembed">%s></a>'%(url,class_prefix,url)
elif proto == 'qr':
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />'%url
return proto+':'+url
return '<img style="width:100px" src="http://chart.apis.google.com/chart?cht=qr&chs=100x100&chl=%s&choe=UTF-8&chld=H" alt="QR Code" title="QR Code" />' % url
return proto + ':' + url
def email_simple(email):
return '<a href="mailto:%s">%s</a>' % (email, email)
return '<a href="mailto:%s">%s</a>' % (email, email)
def render(text,
extra={},
@@ -925,17 +944,19 @@ def render(text,
>>> render("anchor with name 'NEWLINE': [[NEWLINE [newline] ]]")
'<p>anchor with name \\'NEWLINE\\': <span class="anchor" id="markmin_NEWLINE">newline</span></p>'
"""
if autolinks=="default": autolinks = autolinks_simple
if protolinks=="default": protolinks = protolinks_simple
pp='\n' if pretty_print else ''
if isinstance(text,unicode):
if autolinks == "default":
autolinks = autolinks_simple
if protolinks == "default":
protolinks = protolinks_simple
pp = '\n' if pretty_print else ''
if isinstance(text, unicode):
text = text.encode('utf8')
text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
text = text.replace('\x05','').replace('\r\n', '\n') # concatenate strings separeted by \\n
text = text.replace('\x05', '').replace('\r\n', '\n') # concatenate strings separeted by \\n
if URL is not None:
text = replace_at_urls(text,URL)
text = replace_at_urls(text, URL)
if latex == 'google':
text = regex_dd.sub('``\g<latex>``:latex ', text)
@@ -945,9 +966,10 @@ def render(text,
# store them into segments they will be treated as code
#############################################################
segments = []
def mark_code(m):
g = m.group(0)
if g in (META, DISABLED_META ):
if g in (META, DISABLED_META):
segments.append((None, None, None, g))
return m.group()
elif g == '````':
@@ -956,10 +978,12 @@ def render(text,
else:
c = m.group('c') or ''
p = m.group('p') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
code = m.group('t').replace('!`!','`')
if 'code' in allowed and c not in allowed['code']:
c = ''
code = m.group('t').replace('!`!', '`')
segments.append((code, c, p, m.group(0)))
return META
text = regex_code.sub(mark_code, text)
#############################################################
@@ -967,56 +991,58 @@ def render(text,
# store them into links they will be treated as link
#############################################################
links = []
def mark_link(m):
links.append( None if m.group() == LINK
else m.group('s') )
links.append(None if m.group() == LINK
else m.group('s'))
return LINK
text = regex_link.sub(mark_link, text)
text = escape(text)
if protolinks:
text = regex_proto.sub(lambda m: protolinks(*m.group('p','k')), text)
text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text)
if autolinks:
text = replace_autolinks(text,autolinks)
text = replace_autolinks(text, autolinks)
#############################################################
# normalize spaces
#############################################################
strings=text.split('\n')
strings = text.split('\n')
def parse_title(t, s): #out, lev, etags, tag, s):
hlevel=str(len(t))
def parse_title(t, s): # out, lev, etags, tag, s):
hlevel = str(len(t))
out.extend(etags[::-1])
out.append("<h%s>%s"%(hlevel,s))
etags[:]=["</h%s>%s"%(hlevel,pp)]
lev=0
ltags[:]=[]
tlev[:]=[]
out.append("<h%s>%s" % (hlevel, s))
etags[:] = ["</h%s>%s" % (hlevel, pp)]
lev = 0
ltags[:] = []
tlev[:] = []
return (lev, 'h')
def parse_list(t, p, s, tag, lev, mtag, lineno):
lent=len(t)
if lent<lev: # current item level < previous item level
while ltags[-1]>lent:
lent = len(t)
if lent < lev: # current item level < previous item level
while ltags[-1] > lent:
ltags.pop()
out.append(etags.pop())
lev=lent
tlev[lev:]=[]
lev = lent
tlev[lev:] = []
if lent>lev: # current item level > previous item level
if lev==0: # previous line is not a list (paragraph or title)
if lent > lev: # current item level > previous item level
if lev == 0: # previous line is not a list (paragraph or title)
out.extend(etags[::-1])
ltags[:]=[]
tlev[:]=[]
etags[:]=[]
if pend and mtag == '.': # paragraph in a list:
ltags[:] = []
tlev[:] = []
etags[:] = []
if pend and mtag == '.': # paragraph in a list:
out.append(etags.pop())
ltags.pop()
for i in xrange(lent-lev):
out.append('<'+tag+'>'+pp)
etags.append('</'+tag+'>'+pp)
lev+=1
for i in xrange(lent - lev):
out.append('<' + tag + '>' + pp)
etags.append('</' + tag + '>' + pp)
lev += 1
ltags.append(lev)
tlev.append(tag)
elif lent == lev:
@@ -1025,22 +1051,22 @@ def render(text,
for i in xrange(ltags.count(lent)):
ltags.pop()
out.append(etags.pop())
tlev[-1]=tag
out.append('<'+tag+'>'+pp)
etags.append('</'+tag+'>'+pp)
tlev[-1] = tag
out.append('<' + tag + '>' + pp)
etags.append('</' + tag + '>' + pp)
ltags.append(lev)
else:
if ltags.count(lev)>1:
if ltags.count(lev) > 1:
out.append(etags.pop())
ltags.pop()
mtag='l'
mtag = 'l'
out.append('<li>')
etags.append('</li>'+pp)
etags.append('</li>' + pp)
ltags.append(lev)
if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if p and mtag=='l':
(lev,mtag,lineno)=parse_point(t, s, lev, '', lineno)
if p and mtag == 'l':
(lev, mtag, lineno) = parse_point(t, s, lev, '', lineno)
else:
out.append(s)
@@ -1048,28 +1074,28 @@ def render(text,
def parse_point(t, s, lev, mtag, lineno):
""" paragraphs in lists """
lent=len(t)
if lent>lev:
lent = len(t)
if lent > lev:
return parse_list(t, '.', s, 'ul', lev, mtag, lineno)
elif lent<lev:
while ltags[-1]>lent:
elif lent < lev:
while ltags[-1] > lent:
ltags.pop()
out.append(etags.pop())
lev=lent
tlev[lev:]=[]
mtag=''
elif lent==lev:
lev = lent
tlev[lev:] = []
mtag = ''
elif lent == lev:
if pend and mtag == '.':
out.append(etags.pop())
ltags.pop()
if br and mtag in ('l','.'):
if br and mtag in ('l', '.'):
out.append(br)
if s == META:
mtag = ''
mtag = ''
else:
mtag = '.'
if s[:1] == '-':
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
if mtag == '.':
out.append(pbeg)
if pend:
@@ -1083,19 +1109,19 @@ def render(text,
# - is empty -> this is an <hr /> tag
# - consists '|' -> table
# - consists other characters -> blockquote
if (lineno+1 >= strings_len or
not(s.count('-') == len(s) and len(s)>3)):
return (s, mtag, lineno)
if (lineno + 1 >= strings_len or
not (s.count('-') == len(s) and len(s) > 3)):
return (s, mtag, lineno)
lineno+=1
lineno += 1
s = strings[lineno].strip()
if s:
if '|' in s:
# table
tout=[]
thead=[]
tbody=[]
rownum=0
tout = []
thead = []
tbody = []
rownum = 0
t_id = ''
t_cls = ''
@@ -1104,14 +1130,14 @@ def render(text,
s = strings[lineno].strip()
if s[:1] == '=':
# header or footer
if s.count('=')==len(s) and len(s)>3:
if not thead: # if thead list is empty:
if s.count('=') == len(s) and len(s) > 3:
if not thead: # if thead list is empty:
thead = tout
else:
tbody.extend(tout)
tout = []
rownum=0
lineno+=1
rownum = 0
lineno += 1
continue
m = regex_tq.match(s)
@@ -1121,36 +1147,36 @@ def render(text,
break
if rownum % 2:
tr = '<tr class="even">'
tr = '<tr class="even">'
else:
tr = '<tr class="first">' if rownum == 0 else '<tr>'
tr = '<tr class="first">' if rownum == 0 else '<tr>'
tout.append(tr + ''.join(['<td%s>%s</td>' % (
' class="num"'
if regex_num.match(f) else '',
f.strip()
) for f in s.split('|')])+'</tr>'+pp)
rownum+=1
lineno+=1
' class="num"'
if regex_num.match(f) else '',
f.strip()
) for f in s.split('|')]) + '</tr>' + pp)
rownum += 1
lineno += 1
t_cls = ' class="%s%s"'%(class_prefix, t_cls) \
t_cls = ' class="%s%s"' % (class_prefix, t_cls) \
if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"'%(id_prefix, t_id) if t_id else ''
t_id = ' id="%s%s"' % (id_prefix, t_id) if t_id else ''
s = ''
if thead:
s += '<thead>'+pp+''.join([l for l in thead])+'</thead>'+pp
if not tbody: # tbody strings are in tout list
s += '<thead>' + pp + ''.join([l for l in thead]) + '</thead>' + pp
if not tbody: # tbody strings are in tout list
tbody = tout
tout = []
if tbody: # if tbody list is not empty:
s += '<tbody>'+pp+''.join([l for l in tbody])+'</tbody>'+pp
if tout: # tfoot is not empty:
s += '<tfoot>'+pp+''.join([l for l in tout])+'</tfoot>'+pp
if tbody: # if tbody list is not empty:
s += '<tbody>' + pp + ''.join([l for l in tbody]) + '</tbody>' + pp
if tout: # tfoot is not empty:
s += '<tfoot>' + pp + ''.join([l for l in tout]) + '</tfoot>' + pp
s = '<table%s%s>%s%s</table>%s' % (t_cls, t_id, pp, s, pp)
mtag='t'
mtag = 't'
else:
# parse blockquote:
bq_begin=lineno
t_mode = False # embedded table
bq_begin = lineno
t_mode = False # embedded table
t_cls = ''
t_id = ''
@@ -1160,57 +1186,57 @@ def render(text,
if not t_mode:
m = regex_tq.match(s)
if m:
if (lineno+1 == strings_len or
'|' not in strings[lineno+1]):
t_cls = m.group('c') or ''
t_id = m.group('p') or ''
break
if (lineno + 1 == strings_len or
'|' not in strings[lineno + 1]):
t_cls = m.group('c') or ''
t_id = m.group('p') or ''
break
if regex_bq_headline.match(s):
if (lineno+1 < strings_len and
strings[lineno+1].strip()):
t_mode = True
lineno+=1
if (lineno + 1 < strings_len and
strings[lineno + 1].strip()):
t_mode = True
lineno += 1
continue
elif regex_tq.match(s):
t_mode=False
lineno+=1
t_mode = False
lineno += 1
continue
lineno+=1
lineno += 1
t_cls = ' class="%s%s"'%(class_prefix,t_cls) \
t_cls = ' class="%s%s"' % (class_prefix, t_cls) \
if t_cls and t_cls != 'id' else ''
t_id = ' id="%s%s"'%(id_prefix,t_id) \
t_id = ' id="%s%s"' % (id_prefix, t_id) \
if t_id else ''
s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls,
t_id,
'\n'.join(strings[bq_begin:lineno]),pp)
mtag='q'
% (t_cls,
t_id,
'\n'.join(strings[bq_begin:lineno]), pp)
mtag = 'q'
else:
s = '<hr />'
lineno-=1
mtag='q'
lineno -= 1
mtag = 'q'
return (s, 'q', lineno)
if sep == 'p':
pbeg = "<p>"
pend = "</p>"+pp
br = ''
pbeg = "<p>"
pend = "</p>" + pp
br = ''
else:
pbeg = pend = ''
br = "<br />"+pp if sep=='br' else ''
pbeg = pend = ''
br = "<br />" + pp if sep == 'br' else ''
lev = 0 # nesting level of lists
c0 = '' # first character of current line
out = [] # list of processed lines
etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag
lev = 0 # nesting level of lists
c0 = '' # first character of current line
out = [] # list of processed lines
etags = [] # trailing tags
ltags = [] # level# correspondent to trailing tag
tlev = [] # list of tags for each level ('ul' or 'ol')
mtag = '' # marked tag (~last tag) ('l','.','h','p','t'). Used to set <br/>
# and to avoid <p></p> around tables and blockquotes
# and to avoid <p></p> around tables and blockquotes
lineno = 0
strings_len = len(strings)
while lineno < strings_len:
@@ -1222,65 +1248,67 @@ def render(text,
#### ++++ ---- .... ------- field | field | field <-body
##### +++++ ----- ..... ---------------------:class[id]
"""
pc0=c0 # first character of previous line
c0=s[:1]
if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - .
(t1,t2,p,ss) = regex_list.findall(s)[0]
pc0 = c0 # first character of previous line
c0 = s[:1]
if c0: # for non empty strings
if c0 in "#+-.": # first character is one of: # + - .
(t1, t2, p, ss) = regex_list.findall(s)[0]
# t1 - tag ("###")
# t2 - tag ("+++", "---", "...")
# p - paragraph point ('.')->for "++." or "--."
# ss - other part of string
if t1 or t2:
# headers and lists:
if c0 == '#': # headers
if c0 == '#': # headers
(lev, mtag) = parse_title(t1, ss)
lineno+=1
lineno += 1
continue
elif c0 == '+': # ordered list
(lev, mtag, lineno)= parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno+=1
elif c0 == '+': # ordered list
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ol', lev, mtag, lineno)
lineno += 1
continue
elif c0 == '-': # unordered list, table or blockquote
elif c0 == '-': # unordered list, table or blockquote
if p or ss:
(lev, mtag, lineno) = parse_list(t2, p, ss, 'ul', lev, mtag, lineno)
lineno+=1
lineno += 1
continue
else:
(s, mtag, lineno) = parse_table_or_blockquote(s, mtag, lineno)
elif lev>0: # and c0 == '.' # paragraph in lists
elif lev > 0: # and c0 == '.' # paragraph in lists
(lev, mtag, lineno) = parse_point(t2, ss, lev, mtag, lineno)
lineno+=1
lineno += 1
continue
if lev == 0 and (mtag == 'q' or s == META):
# new paragraph
pc0=''
pc0 = ''
if pc0 == '' or (mtag != 'p' and s0 not in (' ','\t')):
if pc0 == '' or (mtag != 'p' and s0 not in (' ', '\t')):
# paragraph
out.extend(etags[::-1])
etags=[]
ltags=[]
tlev=[]
lev=0
if br and mtag == 'p': out.append(br)
etags = []
ltags = []
tlev = []
lev = 0
if br and mtag == 'p':
out.append(br)
if mtag != 'q' and s != META:
if pend: etags=[pend]
out.append(pbeg)
mtag = 'p'
if pend:
etags = [pend]
out.append(pbeg)
mtag = 'p'
else:
mtag = ''
mtag = ''
out.append(s)
else:
if lev>0 and mtag=='.' and s == META:
if lev > 0 and mtag == '.' and s == META:
out.append(etags.pop())
ltags.pop()
out.append(s)
mtag = ''
else:
out.append(' '+s)
lineno+=1
out.append(' ' + s)
lineno += 1
out.extend(etags[::-1])
text = ''.join(out)
@@ -1295,7 +1323,7 @@ def render(text,
# deal with images, videos, audios and links
#############################################################
def sub_media(m):
t,a,k,p,w = m.group('t','a','k','p','w')
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
if not k:
return m.group(0)
k = escape(k)
@@ -1305,40 +1333,40 @@ def render(text,
p_begin = p_end = ''
if p == 'center':
p_begin = '<p style="text-align:center">'
p_end = '</p>'+pp
p_end = '</p>' + pp
elif p == 'blockleft':
p_begin = '<p style="text-align:left">'
p_end = '</p>'+pp
p_end = '</p>' + pp
elif p == 'blockright':
p_begin = '<p style="text-align:right">'
p_end = '</p>'+pp
elif p in ('left','right'):
style = ('float:%s' % p)+(';%s' % style if style else '')
p_end = '</p>' + pp
elif p in ('left', 'right'):
style = ('float:%s' % p) + (';%s' % style if style else '')
if t and regex_auto.match(t):
p_begin = p_begin + '<a href="%s">' % t
p_end = '</a>' + p_end
t = ''
if style:
style = ' style="%s"' % style
if p in ('video','audio'):
if p in ('video', 'audio'):
t = render(t, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else ''
% dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"' % escape(t).replace(META, DISABLED_META) if t else ''
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
def sub_link(m):
t,a,k,p = m.group('t','a','k','p')
t, a, k, p = m.group('t', 'a', 'k', 'p')
if not k and not t:
return m.group(0)
t = t or ''
a = escape(a) if a else ''
if k:
if '#' in k and not ':' in k.split('#')[0]:
if '#' in k and ':' not in k.split('#')[0]:
# wikipage, not external url
k=k.replace('#','#'+id_prefix)
k = k.replace('#', '#' + id_prefix)
k = escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else ''
@@ -1347,18 +1375,18 @@ def render(text,
return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \
% dict(k=k, title=title, target=target, t=t)
if t == 'NEWLINE' and not a:
return '<br />'+pp
return '<br />' + pp
return '<span class="anchor" id="%s">%s</span>' % (
escape(id_prefix+t),
render(a, {},{},'br', URL,
escape(id_prefix + t),
render(a, {}, {}, 'br', URL,
environment, latex, autolinks,
protolinks, class_prefix,
id_prefix, pretty_print))
parts = text.split(LINK)
text = parts[0]
for i,s in enumerate(links):
if s == None:
for i, s in enumerate(links):
if s is None:
html = LINK
else:
html = regex_media_level2.sub(sub_media, s)
@@ -1366,51 +1394,53 @@ def render(text,
html = regex_link_level2.sub(sub_link, html)
if html == s:
# return unprocessed string as a signal of an error
html = '[[%s]]'%s
text += html + parts[i+1]
html = '[[%s]]' % s
text += html + parts[i + 1]
#############################################################
# process all code text
#############################################################
def expand_meta(m):
code,b,p,s = segments.pop(0)
if code==None or m.group() == DISABLED_META:
code, b, p, s = segments.pop(0)
if code is None or m.group() == DISABLED_META:
return escape(s)
if b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if p:
return str(extra[b](code,p))
return str(extra[b](code, p))
else:
return str(extra[b](code))
elif b=='cite':
return '['+','.join('<a href="#%s" class="%s">%s</a>' \
% (id_prefix+d,b,d) \
for d in escape(code).split(','))+']'
elif b=='latex':
elif b == 'cite':
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
elif b == 'latex':
return LATEX % urllib.quote(code)
elif b in html_colors:
return '<span style="color: %s">%s</span>' \
% (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
% (b, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
elif b in ('c', 'color') and p:
c=p.split(':')
fg='color: %s;' % c[0] if c[0] else ''
bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else ''
return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else ''
id = ' id="%s%s"'%(id_prefix,escape(p)) if p else ''
beg=(code[:1]=='\n')
end=[None,-1][code[-1:]=='\n']
c = p.split(':')
fg = 'color: %s;' % c[0] if c[0] else ''
bg = 'background-color: %s;' % c[1] if len(c) > 1 and c[1] else ''
return '<span style="%s%s">%s</span>' \
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
id = ' id="%s%s"' % (id_prefix, escape(p)) if p else ''
beg = (code[:1] == '\n')
end = [None, -1][code[-1:] == '\n']
if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text)
if environment:
text = replace_components(text,environment)
text = replace_components(text, environment)
return text.translate(ttab_out)
@@ -1423,16 +1453,18 @@ def markmin2html(text, extra={}, allowed={}, sep='p',
class_prefix=class_prefix, id_prefix=id_prefix,
pretty_print=pretty_print)
def run_doctests():
import doctest
doctest.testmod()
if __name__ == '__main__':
import sys
import doctest
from textwrap import dedent
html=dedent("""
html = dedent("""
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
@@ -1446,7 +1478,7 @@ if __name__ == '__main__':
</html>""")[1:]
if sys.argv[1:2] == ['-h']:
style=dedent("""
style = dedent("""
<style>
blockquote { background-color: #FFFAAE; padding: 7px; }
table { border-collapse: collapse; }
@@ -1467,22 +1499,23 @@ if __name__ == '__main__':
body=markmin2html(__doc__, pretty_print=True))
elif sys.argv[1:2] == ['-t']:
from timeit import Timer
loops=1000
ts = Timer("markmin2html(__doc__)","from markmin2html import markmin2html")
loops = 1000
ts = Timer("markmin2html(__doc__)", "from markmin2html import markmin2html")
print 'timeit "markmin2html(__doc__)":'
t = min([ts.timeit(loops) for i in range(3)])
print "%s loops, best of 3: %.3f ms per loop" % (loops, t/1000*loops)
print "%s loops, best of 3: %.3f ms per loop" % (loops, t / 1000 * loops)
elif len(sys.argv) > 1:
fargv = open(sys.argv[1],'r')
fargv = open(sys.argv[1], 'r')
try:
markmin_text=fargv.read()
markmin_text = fargv.read()
# embed css file from second parameter into html file
if len(sys.argv) > 2:
if sys.argv[2].startswith('@'):
markmin_style = '<link rel="stylesheet" href="'+sys.argv[2][1:]+'"/>'
markmin_style = '<link rel="stylesheet" href="' + sys.argv[2][1:] + '"/>'
else:
fargv2 = open(sys.argv[2],'r')
fargv2 = open(sys.argv[2], 'r')
try:
markmin_style = "<style>\n" + fargv2.read() + "</style>"
finally:
@@ -1496,10 +1529,9 @@ if __name__ == '__main__':
fargv.close()
else:
print "Usage: "+sys.argv[0]+" -h | -t | file.markmin [file.css|@path_to/css]"
print "Usage: " + sys.argv[0] + " -h | -t | file.markmin [file.css|@path_to/css]"
print "where: -h - print __doc__"
print " -t - timeit __doc__ (for testing purpuse only)"
print " file.markmin [file.css] - process file.markmin + built in file.css (optional)"
print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)"
run_doctests()
+138 -117
View File
@@ -7,53 +7,57 @@ import sys
import doctest
from optparse import OptionParser
__all__ = ['render','markmin2latex']
__all__ = ['render', 'markmin2latex']
META = 'META'
regex_newlines = re.compile('(\n\r)|(\r\n)')
regex_dd=re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+')|(``(?P<t>.*?)``(:(?P<c>\w+))?)',re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)',re.M)
regex_dd = re.compile('\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('(' + META + ')|(``(?P<t>.*?)``(:(?P<c>\w+))?)', re.S)
regex_title = re.compile('^#{1} (?P<t>[^\n]+)', re.M)
regex_maps = [
(re.compile('[ \t\r]+\n'),'\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'),'{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"),'{\\it \g<t>}'),
(re.compile('^#{5,6}\s*(?P<t>[^\n]+)',re.M),'\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2}\s*(?P<t>[^\n]+)',re.M),'\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1}\s*(?P<t>[^\n]+)',re.M),''),
(re.compile('^\- +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)',re.M),'\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'),'\n'),
(re.compile('\n\s+\n'),'\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n',re.M|re.S)
(re.compile('[ \t\r]+\n'), '\n'),
(re.compile('\*\*(?P<t>[^\s\*]+( +[^\s\*]+)*)\*\*'), '{\\\\bf \g<t>}'),
(re.compile("''(?P<t>[^\s']+( +[^\s']+)*)''"), '{\\it \g<t>}'),
(re.compile('^#{5,6}\s*(?P<t>[^\n]+)', re.M), '\n\n{\\\\bf \g<t>}\n'),
(re.compile('^#{4}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsubsection{\g<t>}\n'),
(re.compile('^#{3}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\subsection{\g<t>}\n'),
(re.compile('^#{2}\s*(?P<t>[^\n]+)', re.M), '\n\n\\\\goodbreak\\section{\g<t>}\n'),
(re.compile('^#{1}\s*(?P<t>[^\n]+)', re.M), ''),
(re.compile('^\- +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('^\+ +(?P<t>.*)', re.M), '\\\\begin{itemize}\n\\item \g<t>\n\\end{itemize}'),
(re.compile('\\\\end\{itemize\}\s+\\\\begin\{itemize\}'), '\n'),
(re.compile('\n\s+\n'), '\n\n')]
regex_table = re.compile('^\-{4,}\n(?P<t>.*?)\n\-{4,}(:(?P<c>\w+))?\n', re.M | re.S)
regex_anchor = re.compile('\[\[(?P<t>\S+)\]\]')
regex_bibitem = re.compile('\-\s*\[\[(?P<t>\S+)\]\]')
regex_image_width = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center) +(?P<w>\d+px)\]\]')
regex_image = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +(?P<p>left|right|center)\]\]')
#regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
#regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
# regex_video = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +video\]\]')
# regex_audio = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+) +audio\]\]')
regex_link = re.compile('\[\[(?P<t>[^\]]*?) +(?P<k>\S+)\]\]')
regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)',re.M)
regex_auto = re.compile('(?<!["\w])(?P<k>\w+://[\w\.\-\?&%\:]+)', re.M)
regex_commas = re.compile('[ ]+(?P<t>[,;\.])')
regex_noindent = re.compile('\n\n(?P<t>[a-z])')
#regex_quote_left = re.compile('"(?=\w)')
#regex_quote_right = re.compile('(?=\w\.)"')
def latex_escape(text,pound=True):
text=text.replace('\\','{\\textbackslash}')
for c in '^_&$%{}': text=text.replace(c,'\\'+c)
text=text.replace('\\{\\textbackslash\\}','{\\textbackslash}')
if pound: text=text.replace('#','\\#')
# regex_quote_left = re.compile('"(?=\w)')
# regex_quote_right = re.compile('(?=\w\.)"')
def latex_escape(text, pound=True):
text = text.replace('\\', '{\\textbackslash}')
for c in '^_&$%{}':
text = text.replace(c, '\\' + c)
text = text.replace('\\{\\textbackslash\\}', '{\\textbackslash}')
if pound: text = text.replace('#', '\\#')
return text
def render(text,
extra={},
allowed={},
sep='p',
image_mapper=lambda x:x,
image_mapper=lambda x: x,
chapters=False):
#############################################################
# replace all blocks marked with ``...``:class with META
@@ -61,62 +65,68 @@ def render(text,
#############################################################
text = str(text or '')
segments, i = [], 0
text = regex_dd.sub('``\g<latex>``:latex ',text)
text = regex_newlines.sub('\n',text)
text = regex_dd.sub('``\g<latex>``:latex ', text)
text = regex_newlines.sub('\n', text)
while True:
item = regex_code.search(text,i)
if not item: break
if item.group()==META:
segments.append((None,None))
text = text[:item.start()]+META+text[item.end():]
item = regex_code.search(text, i)
if not item:
break
if item.group() == META:
segments.append((None, None))
text = text[:item.start()] + META + text[item.end():]
else:
c = item.group('c') or ''
if 'code' in allowed and not c in allowed['code']: c = ''
code = item.group('t').replace('!`!','`')
segments.append((code,c))
text = text[:item.start()]+META+text[item.end():]
i=item.start()+3
if 'code' in allowed and c not in allowed['code']:
c = ''
code = item.group('t').replace('!`!', '`')
segments.append((code, c))
text = text[:item.start()] + META + text[item.end():]
i = item.start() + 3
#############################################################
# do h1,h2,h3,h4,h5,h6,b,i,ol,ul and normalize spaces
#############################################################
title = regex_title.search(text)
if not title: title='Title'
else: title=title.group('t')
if not title:
title = 'Title'
else:
title = title.group('t')
text = latex_escape(text,pound=False)
text = latex_escape(text, pound=False)
texts = text.split('## References',1)
texts = text.split('## References', 1)
text = regex_anchor.sub('\\label{\g<t>}', texts[0])
if len(texts)==2:
if len(texts) == 2:
text += '\n\\begin{thebibliography}{999}\n'
text += regex_bibitem.sub('\n\\\\bibitem{\g<t>}', texts[1])
text += '\n\\end{thebibliography}\n'
text = '\n'.join(t.strip() for t in text.split('\n'))
for regex, sub in regex_maps:
text = regex.sub(sub,text)
text=text.replace('#','\\#')
text=text.replace('`',"'")
text = regex.sub(sub, text)
text = text.replace('#', '\\#')
text = text.replace('`', "'")
#############################################################
# process tables and blockquotes
#############################################################
while True:
item = regex_table.search(text)
if not item: break
if not item:
break
c = item.group('c') or ''
if 'table' in allowed and not c in allowed['table']: c = ''
if 'table' in allowed and c not in allowed['table']:
c = ''
content = item.group('t')
if ' | ' in content:
rows = content.replace('\n','\\\\\n').replace(' | ',' & ')
row0,row2 = rows.split('\\\\\n',1)
cols=row0.count(' & ')+1
cal='{'+''.join('l' for j in range(cols))+'}'
tabular = '\\begin{center}\n{\\begin{tabular}'+cal+'\\hline\n' + row0+'\\\\ \\hline\n'+row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}'
if row2.count('\n')>20: tabular='\\newpage\n'+tabular
rows = content.replace('\n', '\\\\\n').replace(' | ', ' & ')
row0, row2 = rows.split('\\\\\n', 1)
cols = row0.count(' & ') + 1
cal = '{' + ''.join('l' for j in range(cols)) + '}'
tabular = '\\begin{center}\n{\\begin{tabular}' + cal + '\\hline\n' + row0 + '\\\\ \\hline\n' + row2 + ' \\\\ \\hline\n\\end{tabular}}\n\\end{center}'
if row2.count('\n') > 20:
tabular = '\\newpage\n' + tabular
text = text[:item.start()] + tabular + text[item.end():]
else:
text = text[:item.start()] + '\\begin{quote}' + content + '\\end{quote}' + text[item.end():]
@@ -126,29 +136,32 @@ def render(text,
#############################################################
def sub(x):
f=image_mapper(x.group('k'))
if not f: return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % (f)
text = regex_image_width.sub(sub,text)
text = regex_image.sub(sub,text)
f = image_mapper(x.group('k'))
if not f:
return None
return '\n\\begin{center}\\includegraphics[width=8cm]{%s}\\end{center}\n' % f
text = regex_image_width.sub(sub, text)
text = regex_image.sub(sub, text)
text = regex_link.sub('{\\\\footnotesize\\href{\g<k>}{\g<t>}}', text)
text = regex_commas.sub('\g<t>',text)
text = regex_noindent.sub('\n\\\\noindent \g<t>',text)
text = regex_commas.sub('\g<t>', text)
text = regex_noindent.sub('\n\\\\noindent \g<t>', text)
### fix paths in images
regex=re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
# ## fix paths in images
regex = re.compile('\\\\_\w*\.(eps|png|jpg|gif)')
while True:
match=regex.search(text)
if not match: break
text=text[:match.start()]+text[match.start()+1:]
#text = regex_quote_left.sub('``',text)
#text = regex_quote_right.sub("''",text)
match = regex.search(text)
if not match:
break
text = text[:match.start()] + text[match.start() + 1:]
# text = regex_quote_left.sub('``',text)
# text = regex_quote_right.sub("''",text)
if chapters:
text=text.replace(r'\section*{',r'\chapter*{')
text=text.replace(r'\section{',r'\chapter{')
text=text.replace(r'subsection{',r'section{')
text = text.replace(r'\section*{', r'\chapter*{')
text = text.replace(r'\section{', r'\chapter{')
text = text.replace(r'subsection{', r'section{')
#############################################################
# process all code text
@@ -156,57 +169,64 @@ def render(text,
parts = text.split(META)
text = parts[0]
authors = []
for i,(code,b) in enumerate(segments):
if code==None:
for i, (code, b) in enumerate(segments):
if code is None:
html = META
else:
if b=='hidden':
html=''
elif b=='author':
if b == 'hidden':
html = ''
elif b == 'author':
author = latex_escape(code.strip())
authors.append(author)
html=''
elif b=='inxx':
html='\inxx{%s}' % latex_escape(code)
elif b=='cite':
html='~\cite{%s}' % latex_escape(code.strip())
elif b=='ref':
html='~\ref{%s}' % latex_escape(code.strip())
elif b=='latex':
html = ''
elif b == 'inxx':
html = '\inxx{%s}' % latex_escape(code)
elif b == 'cite':
html = '~\cite{%s}' % latex_escape(code.strip())
elif b == 'ref':
html = '~\ref{%s}' % latex_escape(code.strip())
elif b == 'latex':
if '\n' in code:
html='\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip()
html = '\n\\begin{equation}\n%s\n\\end{equation}\n' % code.strip()
else:
html='$%s$' % code.strip()
elif b=='latex_eqnarray':
code=code.strip()
code='\\\\'.join(x.replace('=','&=&',1) for x in code.split('\\\\'))
html='\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
html = '$%s$' % code.strip()
elif b == 'latex_eqnarray':
code = code.strip()
code = '\\\\'.join(x.replace('=', '&=&', 1) for x in code.split('\\\\'))
html = '\n\\begin{eqnarray}\n%s\n\\end{eqnarray}\n' % code
elif b.startswith('latex_'):
key=b[6:]
html='\\begin{%s}%s\\end{%s}' % (key,code,key)
key = b[6:]
html = '\\begin{%s}%s\\end{%s}' % (key, code, key)
elif b in extra:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = extra[b](code)
elif code[:1]=='\n' or code[:-1]=='\n':
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
elif code[:1] == '\n' or code[:-1] == '\n':
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
if code.startswith('<') or code.startswith('{{') or code.startswith('http'):
html = '\\begin{lstlisting}[keywords={}]\n%s\n\\end{lstlisting}' % code
else:
html = '\\begin{lstlisting}\n%s\n\\end{lstlisting}' % code
else:
if code[:1]=='\n': code=code[1:]
if code[-1:]=='\n': code=code[:-1]
if code[:1] == '\n':
code = code[1:]
if code[-1:] == '\n':
code = code[:-1]
html = '{\\ft %s}' % latex_escape(code)
try:
text = text+html+parts[i+1]
text = text + html + parts[i + 1]
except:
text = text + '... WIKI PROCESSING ERROR ...'
break
text = text.replace(' ~\\cite','~\\cite')
text = text.replace(' ~\\cite', '~\\cite')
return text, title, authors
WRAPPER = """
\\documentclass[12pt]{article}
\\usepackage{hyperref}
@@ -239,12 +259,14 @@ WRAPPER = """
\\end{document}
"""
def markmin2latex(data, image_mapper=lambda x:x, extra={},
def markmin2latex(data, image_mapper=lambda x: x, extra={},
wrapper=WRAPPER):
body, title, authors = render(data, extra=extra, image_mapper=image_mapper)
author = '\n\\and\n'.join(a.replace('\n','\\\\\n\\footnotesize ') for a in authors)
author = '\n\\and\n'.join(a.replace('\n', '\\\\\n\\footnotesize ') for a in authors)
return wrapper % dict(title=title, author=author, body=body)
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("-i", "--info", dest="info",
@@ -252,40 +274,39 @@ if __name__ == '__main__':
parser.add_option("-t", "--test", dest="test", action="store_true",
default=False)
parser.add_option("-n", "--no_wrapper", dest="no_wrapper",
action="store_true",default=False)
parser.add_option("-c", "--chapters", dest="chapters",action="store_true",
default=False,help="switch section for chapter")
action="store_true", default=False)
parser.add_option("-c", "--chapters", dest="chapters", action="store_true",
default=False, help="switch section for chapter")
parser.add_option("-w", "--wrapper", dest="wrapper", default=False,
help="latex file containing header and footer")
(options, args) = parser.parse_args()
if options.info:
import markmin2html
markmin2latex(markmin2html.__doc__)
elif options.test:
doctest.testmod()
else:
if options.wrapper:
fwrapper = open(options.wrapper,'rb')
fwrapper = open(options.wrapper, 'rb')
try:
wrapper = fwrapper.read()
finally:
fwrapper.close()
elif options.no_wrapper:
wrapper = '%(body)s'
wrapper = '%(body)s'
else:
wrapper = WRAPPER
for f in args:
fargs = open(f,'r')
fargs = open(f, 'r')
content_data = []
try:
content_data.append(fargs.read())
finally:
fargs.close()
content = '\n'.join(content_data)
output= markmin2latex(content,
wrapper=wrapper,
chapters=options.chapters)
output = markmin2latex(content,
wrapper=wrapper,
chapters=options.chapters)
print output
+28 -27
View File
@@ -13,21 +13,22 @@ from markmin2latex import markmin2latex
__all__ = ['markmin2pdf']
def removeall(path):
ERROR_STR= """Error removing %(path)s, %(error)s """
def removeall(path):
ERROR_STR = """Error removing %(path)s, %(error)s """
def rmgeneric(path, __func__):
try:
__func__(path)
except OSError, (errno, strerror):
print ERROR_STR % {'path' : path, 'error': strerror }
print ERROR_STR % {'path': path, 'error': strerror}
files=[path]
files = [path]
while files:
file=files[0]
file = files[0]
if os.path.isfile(file):
f=os.remove
f = os.remove
rmgeneric(file, os.remove)
del files[0]
elif os.path.isdir(file):
@@ -36,7 +37,7 @@ def removeall(path):
rmgeneric(file, os.rmdir)
del files[0]
else:
files = [os.path.join(file,x) for x in nested] + files
files = [os.path.join(file, x) for x in nested] + files
def latex2pdf(latex, pdflatex='pdflatex', passes=3):
@@ -49,13 +50,13 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
- passes: defines how often pdflates should be run in the texfile.
"""
pdflatex=pdflatex
passes=passes
warnings=[]
pdflatex = pdflatex
passes = passes
warnings = []
# setup the envoriment
tmpdir = mkdtemp()
texfile = open(tmpdir+'/test.tex','wb')
texfile = open(tmpdir + '/test.tex', 'wb')
texfile.write(latex)
texfile.seek(0)
texfile.close()
@@ -63,8 +64,8 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
# start doing some work
for i in range(0, passes):
logfd,logname = mkstemp()
outfile=os.fdopen(logfd)
logfd, logname = mkstemp()
outfile = os.fdopen(logfd)
try:
ret = subprocess.call([pdflatex,
'-interaction=nonstopmode',
@@ -75,18 +76,18 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
stderr=subprocess.PIPE)
finally:
outfile.close()
re_errors=re.compile('^\!(.*)$',re.M)
re_warnings=re.compile('^LaTeX Warning\:(.*)$',re.M)
re_errors = re.compile('^\!(.*)$', re.M)
re_warnings = re.compile('^LaTeX Warning\:(.*)$', re.M)
flog = open(logname)
try:
loglines = flog.read()
finally:
flog.close()
errors=re_errors.findall(loglines)
warnings=re_warnings.findall(loglines)
errors = re_errors.findall(loglines)
warnings = re_warnings.findall(loglines)
os.unlink(logname)
pdffile=texfile.rsplit('.',1)[0]+'.pdf'
pdffile = texfile.rsplit('.', 1)[0] + '.pdf'
if os.path.isfile(pdffile):
fpdf = open(pdffile, 'rb')
try:
@@ -100,31 +101,31 @@ def latex2pdf(latex, pdflatex='pdflatex', passes=3):
def markmin2pdf(text, image_mapper=lambda x: None, extra={}):
return latex2pdf(markmin2latex(text,image_mapper=image_mapper, extra=extra))
return latex2pdf(markmin2latex(text, image_mapper=image_mapper, extra=extra))
if __name__ == '__main__':
import sys
import doctest
import markmin2html
if sys.argv[1:2]==['-h']:
if sys.argv[1:2] == ['-h']:
data, warnings, errors = markmin2pdf(markmin2html.__doc__)
if errors:
print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings)
print 'ERRORS:' + '\n'.join(errors)
print 'WARNGINS:' + '\n'.join(warnings)
else:
print data
elif len(sys.argv)>1:
fargv = open(sys.argv[1],'rb')
elif len(sys.argv) > 1:
fargv = open(sys.argv[1], 'rb')
try:
data, warnings, errors = markmin2pdf(fargv.read())
finally:
fargv.close()
if errors:
print 'ERRORS:'+'\n'.join(errors)
print 'WARNGINS:'+'\n'.join(warnings)
print 'ERRORS:' + '\n'.join(errors)
print 'WARNGINS:' + '\n'.join(warnings)
else:
print data
else:
doctest.testmod()
+4 -2
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger("web2py.cache.redis")
locker = thread.allocate_lock()
def RedisCache(*args, **vars):
def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=False, db=None):
"""
Usage example: put in models::
@@ -85,7 +85,9 @@ def RedisCache(*args, **vars):
try:
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisCache, instance_name):
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
setattr(RedisCache, instance_name,
RedisClient(redis_conn=redis_conn, debug=debug,
with_lock=with_lock, fail_gracefully=fail_gracefully))
return getattr(RedisCache, instance_name)
finally:
locker.release()
+71 -57
View File
@@ -9,6 +9,17 @@ Scheduler with redis backend
---------------------------------
"""
import os
import time
import socket
import datetime
import logging
from gluon.utils import web2py_uuid
from gluon.storage import Storage
from gluon.scheduler import *
from gluon.scheduler import _decode_dict
from gluon.contrib.redis_utils import RWatchError
USAGE = """
## Example
@@ -35,11 +46,6 @@ mysched = RScheduler(db, dict(demo1=demo1,demo2=demo2), ...., redis_conn=rconn)
"""
import os
import time
import socket
import datetime
import logging
path = os.getcwd()
@@ -47,20 +53,19 @@ if 'WEB2PY_PATH' not in os.environ:
os.environ['WEB2PY_PATH'] = path
try:
from gluon.contrib.simplejson import loads, dumps
except:
# try external module
from simplejson import loads, dumps
except ImportError:
try:
# try stdlib (Python >= 2.6)
from json import loads, dumps
except:
# fallback to pure-Python module
from gluon.contrib.simplejson import loads, dumps
IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
logger = logging.getLogger('web2py.rscheduler.%s' % IDENTIFIER)
from gluon.utils import web2py_uuid
from gluon.storage import Storage
from gluon.scheduler import *
from gluon.scheduler import _decode_dict
from gluon.contrib.redis_utils import RWatchError
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
POLLING = 'POLLING'
@@ -111,8 +116,7 @@ class RScheduler(Scheduler):
self._application = current.request.application or 'appname'
def _nkey(self, key):
"""Helper to restrict all keys to a namespace
and track them"""
"""Helper to restrict all keys to a namespace and track them."""
prefix = 'w2p:rsched:%s' % self._application
allkeys = '%s:allkeys' % prefix
newkey = "%s:%s" % (prefix, key)
@@ -120,10 +124,7 @@ class RScheduler(Scheduler):
return newkey
def prune_all(self):
"""
Just to be fair and implement a method
that does housekeeping
"""
"""Global housekeeping."""
all_keys = self._nkey('allkeys')
with self.r_server.pipeline() as pipe:
while True:
@@ -148,8 +149,9 @@ class RScheduler(Scheduler):
def send_heartbeat(self, counter):
"""
workers coordination has evolved into something is not that
easy. Here we try to do what we need in a single transaction,
Workers coordination in redis.
It has evolved into something is not that easy.
Here we try to do what we need in a single transaction,
and retry that transaction if something goes wrong
"""
with self.r_server.pipeline() as pipe:
@@ -167,7 +169,9 @@ class RScheduler(Scheduler):
def inner_send_heartbeat(self, counter, pipe):
"""
Does a few things:
Do a few things in the "maintenance" thread.
Specifically:
- registers the workers
- accepts commands sent to workers (KILL, TERMINATE, PICK, DISABLED, etc)
- adjusts sleep
@@ -269,6 +273,8 @@ class RScheduler(Scheduler):
def being_a_ticker(self, pipe):
"""
Elects a ticker.
This is slightly more convoluted than the original
but if far more efficient
"""
@@ -311,7 +317,9 @@ class RScheduler(Scheduler):
def assign_tasks(self, db):
"""
The real beauty. We don't need to ASSIGN tasks, we just put
The real beauty.
We don't need to ASSIGN tasks, we just put
them into the relevant queue
"""
st, sd = db.scheduler_task, db.scheduler_task_deps
@@ -358,26 +366,23 @@ class RScheduler(Scheduler):
(sd.can_visit == False) &
(~sd.task_child.belongs(
db(sd.can_visit == False)._select(sd.task_parent)
)
)
)._select(sd.task_child)
)
)._select(sd.task_child)
no_deps = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
)
)._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) &
(sd.can_visit == False)
)
)
)._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) &
(sd.can_visit == False)
)
)
all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
((st.times_run < st.repeats) | (st.repeats == 0)) &
(st.start_time <= now) &
((st.stop_time == None) | (st.stop_time > now)) &
(st.next_run_time <= now) &
(st.enabled == True) &
(st.id.belongs(no_deps))
@@ -437,6 +442,7 @@ class RScheduler(Scheduler):
logger.info('TICKER: tasks are %s', x)
def pop_task(self, db):
"""Lift a task off a queue."""
r_server = self.r_server
st = self.db.scheduler_task
task = None
@@ -449,8 +455,9 @@ class RScheduler(Scheduler):
self.w_stats.status = POLLING
# polling for 1 minute in total. If more groups are in,
# polling is 1 minute in total
logger.debug(' polling on %s' , group)
task_id = r_server.brpoplpush(queued_list, running_list, timeout=60/len(self.group_names))
logger.debug(' polling on %s', group)
task_id = r_server.brpoplpush(queued_list, running_list,
timeout=60 / len(self.group_names))
logger.debug(' finished polling')
self.w_stats.status = ACTIVE
if task_id:
@@ -464,7 +471,8 @@ class RScheduler(Scheduler):
r_server.lrem(running_list, 0, task_id)
r_server.hdel(running_dict, task_id)
r_server.lrem(queued_list, 0, task_id)
logger.error("we received a task that isn't there (%s)" % task_id)
logger.error("we received a task that isn't there (%s)",
task_id)
return None
break
now = self.now()
@@ -474,7 +482,7 @@ class RScheduler(Scheduler):
db.commit()
logger.debug(' work to do %s', task.id)
else:
logger.info('nothing to do (%s)' % self.w_stats.status)
logger.info('nothing to do')
return None
times_run = task.times_run + 1
if not task.prevent_drift:
@@ -482,9 +490,13 @@ class RScheduler(Scheduler):
seconds=task.period
)
else:
next_run_time = task.start_time + datetime.timedelta(
seconds=task.period * times_run
)
# calc next_run_time based on available slots
# see #1191
next_run_time = task.start_time
secondspassed = self.total_seconds(now - next_run_time)
steps = secondspassed // task.period + 1
next_run_time += datetime.timedelta(seconds=task.period * steps)
if times_run < task.repeats or task.repeats == 0:
# need to run (repeating task)
run_again = True
@@ -527,7 +539,9 @@ class RScheduler(Scheduler):
def report_task(self, task, task_report):
"""
Needs overwriting only because we need to pop from the
Override.
Needs it only because we need to pop from the
running tasks
"""
r_server = self.r_server
@@ -552,12 +566,12 @@ class RScheduler(Scheduler):
logger.debug(' deleting task report in db because of no result')
db(sr.id == task.run_id).delete()
# if there is a stop_time and the following run would exceed it
is_expired = (task.stop_time
and task.next_run_time > task.stop_time
and True or False)
status = (task.run_again and is_expired and EXPIRED
or task.run_again and not is_expired
and QUEUED or COMPLETED)
is_expired = (task.stop_time and
task.next_run_time > task.stop_time and
True or False)
status = (task.run_again and is_expired and EXPIRED or
task.run_again and not is_expired and
QUEUED or COMPLETED)
if task_report.status == COMPLETED:
# assigned calculations
d = dict(status=status,
@@ -573,12 +587,12 @@ class RScheduler(Scheduler):
st_mapping = {'FAILED': 'FAILED',
'TIMEOUT': 'TIMEOUT',
'STOPPED': 'FAILED'}[task_report.status]
status = (task.retry_failed
and task.times_failed < task.retry_failed
and QUEUED or task.retry_failed == -1
and QUEUED or st_mapping)
status = (task.retry_failed and
task.times_failed < task.retry_failed and
QUEUED or task.retry_failed == -1 and
QUEUED or st_mapping)
db(st.id == task.task_id).update(
times_failed=db.scheduler_task.times_failed + 1,
times_failed=st.times_failed + 1,
next_run_time=task.next_run_time,
status=status,
assigned_worker_name=self.worker_name
@@ -590,7 +604,7 @@ class RScheduler(Scheduler):
r_server.hdel(running_dict, task.task_id)
def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions
"""Commodity function to call `pop_task` and trap exceptions.
If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds
"""
@@ -614,8 +628,8 @@ class RScheduler(Scheduler):
time.sleep(0.5)
def get_workers(self, only_ticker=False):
""" Returns a dict holding worker_name : {**columns}
representing all "registered" workers
"""Return a dict holding worker_name : {**columns}
representing all "registered" workers.
only_ticker returns only the worker running as a TICKER,
if there is any
"""
+4 -3
View File
@@ -19,13 +19,13 @@ logger = logging.getLogger("web2py.session.redis")
locker = thread.allocate_lock()
def RedisSession(*args, **vars):
def RedisSession(redis_conn, session_expiry=False, with_lock=False, db=None):
"""
Usage example: put in models::
from gluon.contrib.redis_utils import RConn
rconn = RConn()
from gluon.contrib.redis_session
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession(redis_conn=rconn, with_lock=True, session_expiry=False)
session.connect(request, response, db = sessiondb)
@@ -43,7 +43,8 @@ def RedisSession(*args, **vars):
try:
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisSession, instance_name):
setattr(RedisSession, instance_name, RedisClient(*args, **vars))
setattr(RedisSession, instance_name,
RedisClient(redis_conn, session_expiry=session_expiry, with_lock=with_lock))
return getattr(RedisSession, instance_name)
finally:
locker.release()
+8 -7
View File
@@ -72,13 +72,14 @@ def _default_validators(db, field):
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
return requires
# does not get here for reference and list:reference
if field.unique:
requires.append(validators.IS_NOT_IN_DB(db, field))
sff = ['in', 'do', 'da', 'ti', 'de', 'bo']
if field.notnull and not field_type[:2] in sff:
requires.append(validators.IS_NOT_EMPTY())
elif not field.notnull and field_type[:2] in sff and requires:
requires[0] = validators.IS_EMPTY_OR(requires[0])
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
if (field.notnull or field.unique) and not field_type in excluded_fields:
requires.insert(0, validators.IS_NOT_EMPTY())
elif not field.notnull and not field.unique and requires:
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field in ('string', 'text', 'password') else None)
return requires
from gluon.serializers import custom_json, xml
@@ -92,7 +93,7 @@ DAL.uuid = lambda x: web2py_uuid()
DAL.representers = {
'rows_render': sqlhtml.represent,
'rows_xml': sqlhtml.SQLTABLE
}
}
DAL.Field = Field
DAL.Table = Table
+4 -9
View File
@@ -377,12 +377,7 @@ class Request(Storage):
and callable(rest_action)):
raise HTTP(405, "method not allowed")
try:
vars = request.vars
if method == 'POST' and is_json:
body = request.body.read()
if len(body):
vars = sj.loads(body)
res = rest_action(*request.args, **vars)
res = rest_action(*request.args, **request.vars)
if is_json and not isinstance(res, str):
res = json(res)
return res
@@ -817,7 +812,7 @@ class Session(Storage):
response.session_data_name = 'session_data_%s' % masterapp.lower()
response.session_cookie_expires = cookie_expires
response.session_client = str(request.client).replace(':', '.')
response.session_cookie_key = cookie_key
current._session_cookie_key = cookie_key
response.session_cookie_compression_level = compression_level
# check if there is a session_id in cookies
@@ -1070,7 +1065,7 @@ class Session(Storage):
# if not cookie_key, but session_data_name in cookies
# expire session_data_name from cookies
if not response.session_cookie_key:
if not current._session_cookie_key:
if response.session_data_name in cookies:
rcookies[response.session_data_name] = 'expired'
rcookies[response.session_data_name]['path'] = '/'
@@ -1133,7 +1128,7 @@ class Session(Storage):
name = response.session_data_name
compression_level = response.session_cookie_compression_level
value = secure_dumps(dict(self),
response.session_cookie_key,
current._session_cookie_key,
compression_level=compression_level)
rcookies = response.cookies
rcookies.pop(name, None)
+119 -111
View File
@@ -116,6 +116,7 @@ __all__ = [
DEFAULT_PASSWORD_DISPLAY = '*' * 8
def xmlescape(data, quote=True):
"""
Returns an escaped string of the provided data
@@ -139,12 +140,14 @@ def xmlescape(data, quote=True):
data = cgi.escape(data, quote).replace("'", "&#x27;")
return data
def call_as_list(f, *a, **b):
if not isinstance(f, (list, tuple)):
f = [f]
for item in f:
item(*a, **b)
def truncate_string(text, length, dots='...'):
text = text.decode('utf-8')
if len(text) > length:
@@ -152,27 +155,26 @@ def truncate_string(text, length, dots='...'):
return text
def URL(
a=None,
c=None,
f=None,
r=None,
args=None,
vars=None,
anchor='',
extension=None,
env=None,
hmac_key=None,
hash_vars=True,
salt=None,
user_signature=None,
scheme=None,
host=None,
port=None,
encode_embedded_slash=False,
url_encode=True,
language=None,
):
def URL(a=None,
c=None,
f=None,
r=None,
args=None,
vars=None,
anchor='',
extension=None,
env=None,
hmac_key=None,
hash_vars=True,
salt=None,
user_signature=None,
scheme=None,
host=None,
port=None,
encode_embedded_slash=False,
url_encode=True,
language=None
):
"""
generates a url '/a/c/f' corresponding to application a, controller c
and function f. If r=request is passed, a, c, f are set, respectively,
@@ -256,10 +258,6 @@ def URL(
>>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
'/a/c/f#%25%28id%29d'
"""
from rewrite import url_out # done here in case used not-in web2py
@@ -310,7 +308,7 @@ def URL(
else:
function = f
# if the url gets a static resource, don't force extention
# if the url gets a static resource, don't force extension
if controller == 'static':
extension = None
# add static version to url
@@ -319,7 +317,7 @@ def URL(
response = current.response
if response.static_version and response.static_version_urls:
args = [function] + args
function = '_'+str(response.static_version)
function = '_' + str(response.static_version)
if '.' in function:
function, extension = function.rsplit('.', 1)
@@ -332,18 +330,16 @@ def URL(
if args:
if url_encode:
if encode_embedded_slash:
other = '/' + '/'.join([urllib.quote(str(
x), '') for x in args])
other = '/' + '/'.join([urllib.quote(str(x), '') for x in args])
else:
other = args and urllib.quote(
'/' + '/'.join([str(x) for x in args]))
other = args and urllib.quote('/' + '/'.join([str(x) for x in args]))
else:
other = args and ('/' + '/'.join([str(x) for x in args]))
else:
other = ''
if other.endswith('/'):
other += '/' # add trailing slash to make last trailing empty arg explicit
other += '/' # add trailing slash to make last trailing empty arg explicit
list_vars = []
for (key, vals) in sorted(vars.items()):
@@ -366,11 +362,11 @@ def URL(
h_args = '/%s/%s/%s%s' % (application, controller, function2, other)
# how many of the vars should we include in our hash?
if hash_vars is True: # include them all
if hash_vars is True: # include them all
h_vars = list_vars
elif hash_vars is False: # include none of them
elif hash_vars is False: # include none of them
h_vars = ''
else: # include just those specified
else: # include just those specified
if hash_vars and not isinstance(hash_vars, (list, tuple)):
hash_vars = [hash_vars]
h_vars = [(k, v) for (k, v) in list_vars if k in hash_vars]
@@ -439,7 +435,7 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
"""
if not '_signature' in request.get_vars:
if '_signature' not in request.get_vars:
return False # no signature in the request URL
# check if user_signature requires
@@ -539,19 +535,24 @@ class XmlComponent(object):
return CAT(*components)
def add_class(self, name):
""" add a class to _class attribute """
"""
add a class to _class attribute
"""
c = self['_class']
classes = (set(c.split()) if c else set()) | set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
def remove_class(self, name):
""" remove a class from _class attribute """
"""
remove a class from _class attribute
"""
c = self['_class']
classes = (set(c.split()) if c else set()) - set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
class XML(XmlComponent):
"""
use it to wrap a string that contains XML/HTML so that it will not be
@@ -660,11 +661,11 @@ class XML(XmlComponent):
"""
to be considered experimental since the behavior of this method
is questionable
another option could be `TAG(self.text).elements(*args,**kwargs)`
another option could be `TAG(self.text).elements(*args, **kwargs)`
"""
return []
### important to allow safe session.flash=T(....)
# ## important to allow safe session.flash=T(....)
def XML_unpickle(data):
@@ -757,7 +758,7 @@ class DIV(XmlComponent):
Examples:
>>> a=DIV()
>>> a.insert(0,SPAN('x'))
>>> a.insert(0, SPAN('x'))
>>> print a
<div><span>x</span></div>
"""
@@ -853,7 +854,7 @@ class DIV(XmlComponent):
"""
components = []
for c in self.components:
if isinstance(c, (allowed_parents,CAT)):
if isinstance(c, (allowed_parents, CAT)):
pass
elif wrap_lambda:
c = wrap_lambda(c)
@@ -952,7 +953,6 @@ class DIV(XmlComponent):
# get the xml for the inner components
co = join([xmlescape(component) for component in
self.components])
return (fa, co)
def xml(self):
@@ -987,7 +987,7 @@ class DIV(XmlComponent):
Examples:
>>> markdown = lambda text,tag=None,attributes={}: \
>>> markdown = lambda text, tag=None, attributes={}: \
{None: re.sub('\s+',' ',text), \
'h1':'#'+text+'\\n\\n', \
'p':text+'\\n'}.get(tag,text)
@@ -1024,7 +1024,7 @@ class DIV(XmlComponent):
Examples:
>>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
>>> for c in a.elements('span',first_only=True): c[0]='z'
>>> for c in a.elements('span', first_only=True): c[0]='z'
>>> print a
<div><div><span>z</span>3<div><span>y</span></div></div></div>
>>> for c in a.elements('span'): c[0]='z'
@@ -1056,7 +1056,7 @@ class DIV(XmlComponent):
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
>>> print a
>>> print a # We should .xml() here instead of print
<div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
"replace" can be a callable, which will be passed the original element and
@@ -1168,13 +1168,13 @@ class DIV(XmlComponent):
return i
else:
self[i] = replace(self[i]) if callable(replace) else replace
return i+1
return i + 1
# loop the components
if find_text or find_components:
i = 0
while i<len(self.components):
while i < len(self.components):
c = self[i]
j = i+1
j = i + 1
if check and find_text and isinstance(c, str) and \
((is_regex and find_text.search(c)) or (str(find_text) in c)):
j = replace_component(i)
@@ -1265,6 +1265,7 @@ class __tag_div__(DIV):
copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
class __TAG__(XmlComponent):
"""
@@ -1589,6 +1590,7 @@ class A(DIV):
self['_data-w2p_pre_call'] = self['pre_call']
return DIV.xml(self)
class BUTTON(DIV):
tag = 'button'
@@ -1863,11 +1865,11 @@ class INPUT(DIV):
print traceback.format_exc()
msg = "Validation error, field:%s %s" % (name,validator)
raise Exception(msg)
if not errors is None:
if errors is not None:
self.vars[name] = value
self.errors[name] = errors
break
if not name in self.errors:
if name not in self.errors:
self.vars[name] = value
return True
return False
@@ -1882,7 +1884,7 @@ class INPUT(DIV):
_value = None
else:
_value = str(self['_value'])
if '_checked' in self.attributes and not 'value' in self.attributes:
if '_checked' in self.attributes and 'value' not in self.attributes:
pass
elif t == 'checkbox':
if not _value:
@@ -1912,8 +1914,7 @@ class INPUT(DIV):
if name and hasattr(self, 'errors') \
and self.errors.get(name, None) \
and self['hideerror'] != True:
self['_class'] = (self['_class'] and self['_class']
+ ' ' or '') + 'invalidinput'
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
return DIV.xml(self) + DIV(
DIV(
self.errors[name], _class='error',
@@ -1941,11 +1942,11 @@ class TEXTAREA(INPUT):
tag = 'textarea'
def _postprocessing(self):
if not '_rows' in self.attributes:
if '_rows' not in self.attributes:
self['_rows'] = 10
if not '_cols' in self.attributes:
if '_cols' not in self.attributes:
self['_cols'] = 40
if not self['value'] is None:
if self['value'] is not None:
self.components = [self['value']]
elif self.components:
self['value'] = self.components[0]
@@ -1956,7 +1957,7 @@ class OPTION(DIV):
tag = 'option'
def _fixup(self):
if not '_value' in self.attributes:
if '_value' not in self.attributes:
self.attributes['_value'] = str(self.components[0])
@@ -2012,11 +2013,10 @@ class SELECT(INPUT):
options = itertools.chain(*component_list)
value = self['value']
if not value is None:
if value is not None:
if not self['_multiple']:
for c in options: # my patch
if ((value is not None) and
(str(c['_value']) == str(value))):
if ((value is not None) and (str(c['_value']) == str(value))):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2026,8 +2026,7 @@ class SELECT(INPUT):
else:
values = [str(value)]
for c in options: # my patch
if ((value is not None) and
(str(c['_value']) in values)):
if ((value is not None) and (str(c['_value']) in values)):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2077,16 +2076,15 @@ class FORM(DIV):
def assert_status(self, status, request_vars):
return status
def accepts(
self,
request_vars,
session=None,
formname='default',
keepvalues=False,
onvalidation=None,
hideerror=False,
**kwargs
):
def accepts(self,
request_vars,
session=None,
formname='default',
keepvalues=False,
onvalidation=None,
hideerror=False,
**kwargs
):
"""
kwargs is not used but allows to specify the same interface for FORM and SQLFORM
"""
@@ -2128,8 +2126,7 @@ class FORM(DIV):
onsuccess = onvalidation.get('onsuccess', None)
onfailure = onvalidation.get('onfailure', None)
onchange = onvalidation.get('onchange', None)
if [k for k in onvalidation if not k in (
'onsuccess', 'onfailure', 'onchange')]:
if [k for k in onvalidation if k not in ('onsuccess', 'onfailure', 'onchange')]:
raise RuntimeError('Invalid key in onvalidate dict')
if onsuccess and status:
call_as_list(onsuccess, self)
@@ -2144,7 +2141,7 @@ class FORM(DIV):
call_as_list(onvalidation, self)
if self.errors:
status = False
if not session is None:
if session is not None:
if hasattr(self, 'record_hash'):
formkey = self.record_hash + ':' + web2py_uuid()
else:
@@ -2158,25 +2155,22 @@ class FORM(DIV):
return status
def _postprocessing(self):
if not '_action' in self.attributes:
if '_action' not in self.attributes:
self['_action'] = '#'
if not '_method' in self.attributes:
if '_method' not in self.attributes:
self['_method'] = 'post'
if not '_enctype' in self.attributes:
if '_enctype' not in self.attributes:
self['_enctype'] = 'multipart/form-data'
def hidden_fields(self):
c = []
attr = self.attributes.get('hidden', {})
if 'hidden' in self.attributes:
c = [INPUT(_type='hidden', _name=key, _value=value)
for (key, value) in attr.iteritems()]
c = [INPUT(_type='hidden', _name=key, _value=value) for (key, value) in attr.iteritems()]
if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey',
_value=self.formkey))
c.append(INPUT(_type='hidden', _name='_formkey', _value=self.formkey))
if hasattr(self, 'formname') and self.formname:
c.append(INPUT(_type='hidden', _name='_formname',
_value=self.formname))
c.append(INPUT(_type='hidden', _name='_formname', _value=self.formname))
return DIV(c, _style="display:none;")
def xml(self):
@@ -2221,8 +2215,7 @@ class FORM(DIV):
kwargs['request_vars'] = kwargs.get(
'request_vars', current.request.post_vars)
kwargs['session'] = kwargs.get('session', current.session)
kwargs['dbio'] = kwargs.get('dbio', False)
# necessary for SQLHTML forms
kwargs['dbio'] = kwargs.get('dbio', False) # necessary for SQLHTML forms
onsuccess = kwargs.get('onsuccess', 'flash')
onfailure = kwargs.get('onfailure', 'flash')
@@ -2301,8 +2294,7 @@ class FORM(DIV):
"""
kwargs['dbio'] = kwargs.get('dbio', True)
# necessary for SQLHTML forms
kwargs['dbio'] = kwargs.get('dbio', True) # necessary for SQLHTML forms
self.validate(**kwargs)
return self
@@ -2348,10 +2340,9 @@ class FORM(DIV):
def sanitizer(obj):
if isinstance(obj, dict):
for k in obj.keys():
if any([unsafe in str(k).upper() for
unsafe in UNSAFE]):
# erease unsafe pair
obj.pop(k)
if any([unsafe in str(k).upper() for unsafe in UNSAFE]):
# erease unsafe pair
obj.pop(k)
else:
# not implemented
pass
@@ -2377,8 +2368,10 @@ class FORM(DIV):
return [flatten(item) for item in newobj]
else:
return newobj
else: return str(newobj)
else: return newobj
else:
return str(newobj)
else:
return newobj
return flatten(d)
def as_json(self, sanitize=True):
@@ -2505,19 +2498,19 @@ class MENU(DIV):
self.data = data
self.attributes = args
self.components = []
if not '_class' in self.attributes:
if '_class' not in self.attributes:
self['_class'] = 'web2py-menu web2py-menu-vertical'
if not 'ul_class' in self.attributes:
if 'ul_class' not in self.attributes:
self['ul_class'] = 'web2py-menu-vertical'
if not 'li_class' in self.attributes:
if 'li_class' not in self.attributes:
self['li_class'] = 'web2py-menu-expand'
if not 'li_first' in self.attributes:
if 'li_first' not in self.attributes:
self['li_first'] = 'web2py-menu-first'
if not 'li_last' in self.attributes:
if 'li_last' not in self.attributes:
self['li_last'] = 'web2py-menu-last'
if not 'li_active' in self.attributes:
if 'li_active' not in self.attributes:
self['li_active'] = 'web2py-menu-active'
if not 'mobile' in self.attributes:
if 'mobile' not in self.attributes:
self['mobile'] = False
def serialize(self, data, level=0):
@@ -2577,7 +2570,7 @@ class MENU(DIV):
item[3], select, prefix=CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value'
# avoid to wrap the select if no custom items are present
html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select
html = DIV(select, self.serialize(custom_items)) if len(custom_items) else select
return html
def xml(self):
@@ -2587,12 +2580,11 @@ class MENU(DIV):
return self.serialize(self.data, 0).xml()
def embed64(
filename=None,
file=None,
data=None,
extension='image/gif',
):
def embed64(filename=None,
file=None,
data=None,
extension='image/gif'
):
"""
helper to encode the provided (binary) data into base64.
@@ -2610,6 +2602,7 @@ def embed64(
return 'data:%s;base64,%s' % (extension, data)
# TODO: Check if this test() is still relevant now that we have gluon/tests/test_html.py
def test():
"""
Example:
@@ -2809,7 +2802,7 @@ class MARKMIN(XmlComponent):
self.extra = extra or {}
self.allowed = allowed or {}
self.sep = sep
self.url = URL if url == True else url
self.url = URL if url is True else url
self.environment = environment
self.latex = latex
self.autolinks = autolinks
@@ -2833,11 +2826,26 @@ class MARKMIN(XmlComponent):
def __str__(self):
return self.xml()
def ASSIGNJS(**kargs):
"""
Example:
ASSIGNJS(var1='1', var2='2') will return the following javascript variables assignations :
var var1 = "1";
var var2 = "2";
Args:
**kargs: Any keywords arguments and assigned values.
Returns:
Javascript vars assignations for the key/value passed.
"""
from gluon.serializers import json
s = ""
for key, value in kargs.items():
s+='var %s = %s;\n' % (key, json(value))
s += 'var %s = %s;\n' % (key, json(value))
return XML(s)
+26
View File
@@ -61,7 +61,13 @@ PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))'
PY_M_STRING_LITERAL_RE = r'(?<=[^\w]T\.M\()(?P<name>'\
+ r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))'
regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL)
regex_translate_m = re.compile(PY_M_STRING_LITERAL_RE, re.DOTALL)
regex_param = re.compile(r'{(?P<s>.+?)}')
# pattern for a valid accept_language
@@ -960,6 +966,7 @@ def findT(path, language=DEFAULT_LANGUAGE):
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
data = read_locked(filename)
items = regex_translate.findall(data)
items += regex_translate_m.findall(data)
for item in items:
try:
message = safe_eval(item)
@@ -995,6 +1002,25 @@ def update_all_languages(application_path):
findT(application_path, language[:-3])
def update_from_langfile(target, source, force_update=False):
"""this will update untranslated messages in target from source (where both are language files)
this can be used as first step when creating language file for new but very similar language
or if you want update your app from welcome app of newer web2py version
or in non-standard scenarios when you work on target and from any reason you have partial translation in source
Args:
force_update: if False existing translations remain unchanged, if True existing translations will update from source
"""
src = read_dict(source)
sentences = read_dict(target)
for key in sentences:
val = sentences[key]
if not val or val == key or force_update:
new_val = src.get(key)
if new_val and new_val != val:
sentences[key] = new_val
write_dict(target, sentences)
if __name__ == '__main__':
import doctest
doctest.testmod()
+3 -4
View File
@@ -80,10 +80,9 @@ locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
exists = os.path.exists
pjoin = os.path.join
logpath = abspath("logging.conf")
if exists(logpath):
try:
logging.config.fileConfig(abspath("logging.conf"))
else:
except: # fails on GAE or when logfile is missing
logging.basicConfig()
logger = logging.getLogger("web2py")
@@ -361,7 +360,7 @@ def wsgibase(environ, responder):
local_hosts = global_settings.local_hosts
client = get_client(env)
x_req_with = str(env.http_x_requested_with).lower()
cmd_opts = request.global_settings.cmd_options
cmd_opts = global_settings.cmd_options
request.update(
client = client,
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -642,7 +642,7 @@ def regex_url_in(request, environ):
items = filename.split('/', 1)
if regex_version.match(items[0]):
version, filename = items
static_folder = pjoin(request.env.applications_parent,
static_folder = pjoin(global_settings.applications_parent,
'applications', application, 'static')
static_file = os.path.abspath(pjoin(static_folder, filename))
if not static_file.startswith(static_folder):
@@ -947,7 +947,7 @@ class MapUrlIn(object):
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,
root_static_file = pjoin(global_settings.applications_parent,
'applications', self.application,
self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file)
@@ -1016,11 +1016,11 @@ class MapUrlIn(object):
# if language-specific file doesn't exist, try same file in static
#
if self.language:
static_file = pjoin(self.request.env.applications_parent,
static_file = pjoin(global_settings.applications_parent,
'applications', self.application,
'static', self.language, file)
if not self.language or not isfile(static_file):
static_file = pjoin(self.request.env.applications_parent,
static_file = pjoin(global_settings.applications_parent,
'applications', self.application,
'static', file)
self.extension = None
+94 -110
View File
@@ -9,6 +9,26 @@ Background processes made simple
---------------------------------
"""
import os
import time
import multiprocessing
import sys
import threading
import traceback
import signal
import socket
import datetime
import logging
import optparse
import tempfile
import types
import Queue
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid
from gluon.storage import Storage
USAGE = """
## Example
@@ -67,20 +87,6 @@ sudo restart web2py-scheduler
sudo status web2py-scheduler
"""
import os
import time
import multiprocessing
import sys
import threading
import traceback
import signal
import socket
import datetime
import logging
import optparse
import types
import Queue
path = os.getcwd()
if 'WEB2PY_PATH' not in os.environ:
@@ -101,12 +107,6 @@ IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
from gluon import DAL, Field, IS_NOT_EMPTY, IS_IN_SET, IS_NOT_IN_DB
from gluon import IS_INT_IN_RANGE, IS_DATETIME, IS_IN_DB
from gluon.utils import web2py_uuid
from gluon.storage import Storage
QUEUED = 'QUEUED'
ASSIGNED = 'ASSIGNED'
RUNNING = 'RUNNING'
@@ -168,24 +168,25 @@ class TaskReport(object):
class JobGraph(object):
"""Experimental: with JobGraph you can specify
dependencies amongs tasks"""
"""Experimental: dependencies amongs tasks"""
def __init__(self, db, job_name):
self.job_name = job_name or 'job_0'
self.db = db
def add_deps(self, task_parent, task_child):
"""Creates a dependency between task_parent and task_child"""
"""Create a dependency between task_parent and task_child."""
self.db.scheduler_task_deps.insert(task_parent=task_parent,
task_child=task_child,
job_name=self.job_name)
def validate(self, job_name):
"""Validates if all tasks job_name can be completed, i.e. there
are no mutual dependencies among tasks.
def validate(self, job_name=None):
"""Validate if all tasks job_name can be completed.
Checks if there are no mutual dependencies among tasks.
Commits at the end if successfull, or it rollbacks the entire
transaction. Handle with care!"""
transaction. Handle with care!
"""
db = self.db
sd = db.scheduler_task_deps
if job_name:
@@ -223,14 +224,6 @@ class JobGraph(object):
db.rollback()
return None
def demo_function(*argv, **kwargs):
""" test function """
for i in range(argv[0]):
print 'click', i
time.sleep(1)
return 'done'
# the two functions below deal with simplejson decoding as unicode, esp for the dict decode
# and subsequent usage as function Keyword arguments unicode variable names won't work!
# borrowed from http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-unicode-ones-from-json-in-python
@@ -261,11 +254,12 @@ def _decode_dict(dct):
def executor(queue, task, out):
"""The function used to execute tasks in the background process"""
"""The function used to execute tasks in the background process."""
logger.debug(' task started')
class LogOutput(object):
"""Facility to log output at intervals"""
"""Facility to log output at intervals."""
def __init__(self, out_queue):
self.out_queue = out_queue
self.stdout = sys.stdout
@@ -280,7 +274,11 @@ def executor(queue, task, out):
def write(self, data):
self.out_queue.put(data)
W2P_TASK = Storage({'id': task.task_id, 'uuid': task.uuid})
W2P_TASK = Storage({
'id': task.task_id,
'uuid': task.uuid,
'run_id': task.run_id
})
stdout = LogOutput(out)
try:
if task.app:
@@ -318,6 +316,11 @@ def executor(queue, task, out):
result = eval(task.function)(
*loads(task.args, object_hook=_decode_dict),
**loads(task.vars, object_hook=_decode_dict))
if len(result) >= 1024:
fd, temp_path = tempfile.mkstemp(suffix='.w2p_sched')
with os.fdopen(fd, 'w') as f:
f.write(result)
result = 'w2p_special:%s' % temp_path
queue.put(TaskReport('COMPLETED', result=result))
except BaseException, e:
tb = traceback.format_exc()
@@ -335,7 +338,7 @@ class MetaScheduler(threading.Thread):
self.empty_runs = 0
def async(self, task):
"""Starts the background process
"""Start the background process.
Args:
task : a `Task` object
@@ -410,6 +413,12 @@ class MetaScheduler(threading.Thread):
else:
logger.debug(' task completed or failed')
tr = queue.get()
result = tr.result
if result and result.startswith('w2p_special'):
temp_path = result.replace('w2p_special:', '', 1)
with open(temp_path) as f:
tr.result = f.read()
os.unlink(temp_path)
tr.output = task_output
return tr
@@ -444,50 +453,23 @@ class MetaScheduler(threading.Thread):
self.start()
def send_heartbeat(self, counter):
print 'thum'
time.sleep(1)
raise NotImplementedError
def pop_task(self):
"""Fetches a task ready to be executed"""
return Task(
app=None,
function='demo_function',
timeout=7,
args='[2]',
vars='{}')
raise NotImplementedError
def report_task(self, task, task_report):
"""Creates a task report"""
print 'reporting task'
pass
raise NotImplementedError
def sleep(self):
pass
raise NotImplementedError
def loop(self):
"""Main loop, fetching tasks and starting executor's background
processes"""
try:
self.start_heartbeats()
while True and self.have_heartbeat:
logger.debug('looping...')
task = self.pop_task()
if task:
self.empty_runs = 0
self.report_task(task, self.async(task))
else:
self.empty_runs += 1
logger.debug('sleeping...')
if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s',
self.empty_runs, self.max_empty_runs)
if self.empty_runs >= self.max_empty_runs:
logger.info(
'empty runs limit reached, killing myself')
self.die()
self.sleep()
except KeyboardInterrupt:
self.die()
raise NotImplementedError
TASK_STATUS = (QUEUED, RUNNING, COMPLETED, FAILED, TIMEOUT, STOPPED, EXPIRED)
@@ -594,11 +576,11 @@ class Scheduler(MetaScheduler):
return True
def now(self):
"""Shortcut that fetches current time based on UTC preferences"""
"""Shortcut that fetches current time based on UTC preferences."""
return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now()
def set_requirements(self, scheduler_task):
"""Called to set defaults for lazy_tables connections"""
"""Called to set defaults for lazy_tables connections."""
from gluon import current
if hasattr(current, 'request'):
scheduler_task.application_name.default = '%s/%s' % (
@@ -606,7 +588,7 @@ class Scheduler(MetaScheduler):
)
def define_tables(self, db, migrate):
"""Defines Scheduler tables structure"""
"""Define Scheduler tables structure."""
from pydal.base import DEFAULT
logger.debug('defining tables (migrate=%s)', migrate)
now = self.now
@@ -693,14 +675,14 @@ class Scheduler(MetaScheduler):
@staticmethod
def total_seconds(td):
# backport for py2.6
"""Backport for py2.6."""
if hasattr(td, 'total_seconds'):
return td.total_seconds()
else:
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6
def loop(self, worker_name=None):
"""Main loop
"""Main loop.
This works basically as a neverending loop that:
@@ -752,7 +734,8 @@ class Scheduler(MetaScheduler):
self.die()
def wrapped_assign_tasks(self, db):
"""Commodity function to call `assign_tasks` and trap exceptions
"""Commodity function to call `assign_tasks` and trap exceptions.
If an exception is raised, assume it happened because of database
contention and retries `assign_task` after 0.5 seconds
"""
@@ -773,7 +756,8 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions
"""Commodity function to call `pop_task` and trap exceptions.
If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds
"""
@@ -793,7 +777,7 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
def pop_task(self, db):
"""Grabs a task ready to be executed from the queue"""
"""Grab a task ready to be executed from the queue."""
now = self.now()
st = self.db.scheduler_task
if self.is_a_ticker and self.do_assign_tasks:
@@ -874,7 +858,8 @@ class Scheduler(MetaScheduler):
uuid=task.uuid)
def wrapped_report_task(self, task, task_report):
"""Commodity function to call `report_task` and trap exceptions
"""Commodity function to call `report_task` and trap exceptions.
If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds
"""
@@ -891,8 +876,10 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
def report_task(self, task, task_report):
"""Takes care of storing the result according to preferences
and deals with logic for repeating tasks"""
"""Take care of storing the result according to preferences.
Deals with logic for repeating tasks.
"""
db = self.db
now = self.now()
st = db.scheduler_task
@@ -914,12 +901,12 @@ class Scheduler(MetaScheduler):
logger.debug(' deleting task report in db because of no result')
db(sr.id == task.run_id).delete()
# if there is a stop_time and the following run would exceed it
is_expired = (task.stop_time
and task.next_run_time > task.stop_time
and True or False)
status = (task.run_again and is_expired and EXPIRED
or task.run_again and not is_expired
and QUEUED or COMPLETED)
is_expired = (task.stop_time and
task.next_run_time > task.stop_time and
True or False)
status = (task.run_again and is_expired and EXPIRED or
task.run_again and not is_expired and
QUEUED or COMPLETED)
if task_report.status == COMPLETED:
d = dict(status=status,
next_run_time=task.next_run_time,
@@ -945,27 +932,26 @@ class Scheduler(MetaScheduler):
logger.info('task completed (%s)', task_report.status)
def update_dependencies(self, db, task_id):
"""Unblock execution paths for Jobs."""
db(db.scheduler_task_deps.task_child == task_id).update(can_visit=True)
def adj_hibernation(self):
"""Used to increase the "sleep" interval for DISABLED workers"""
"""Used to increase the "sleep" interval for DISABLED workers."""
if self.w_stats.status == DISABLED:
wk_st = self.w_stats.sleep
hibernation = wk_st + HEARTBEAT if wk_st < MAXHIBERNATION else MAXHIBERNATION
self.w_stats.sleep = hibernation
def send_heartbeat(self, counter):
"""This function is vital for proper coordination among available
workers.
It:
"""Coordination among available workers.
It:
- sends the heartbeat
- elects a ticker among available workers (the only process that
effectively dispatch tasks to workers)
- deals with worker's statuses
- does "housecleaning" for dead workers
- triggers tasks assignment to workers
"""
if not self.db_thread:
logger.debug('thread building own DAL object')
@@ -1053,7 +1039,8 @@ class Scheduler(MetaScheduler):
self.sleep()
def being_a_ticker(self):
"""Elects a TICKER process that assigns tasks to available workers.
"""Elect a TICKER process that assigns tasks to available workers.
Does its best to elect a worker that is not busy processing other tasks
to allow a proper distribution of tasks among all active workers ASAP
"""
@@ -1087,7 +1074,7 @@ class Scheduler(MetaScheduler):
return False
def assign_tasks(self, db):
"""Assigns task to workers, that can then pop them from the queue
"""Assign task to workers, that can then pop them from the queue.
Deals with group_name(s) logic, in order to assign linearly tasks
to available workers for those groups
@@ -1137,9 +1124,6 @@ class Scheduler(MetaScheduler):
all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
((st.times_run < st.repeats) | (st.repeats == 0)) &
(st.start_time <= now) &
((st.stop_time == None) | (st.stop_time > now)) &
(st.next_run_time <= now) &
(st.enabled == True) &
(st.id.belongs(no_deps))
@@ -1151,8 +1135,8 @@ class Scheduler(MetaScheduler):
# intelligence (like esteeming how many tasks will a worker complete
# before the ticker reassign them around, but the gain is quite small
# 50 is a sweet spot also for fast tasks, with sane heartbeat values
# NB: ticker reassign tasks every 5 cycles, so if a worker completes its
# 50 tasks in less than heartbeat*5 seconds,
# NB: ticker reassign tasks every 5 cycles, so if a worker completes
# its 50 tasks in less than heartbeat*5 seconds,
# it won't pick new tasks until heartbeat*5 seconds pass.
# If a worker is currently elaborating a long task, its tasks needs to
@@ -1165,7 +1149,7 @@ class Scheduler(MetaScheduler):
x = 0
for group in wkgroups.keys():
tasks = all_available(st.group_name == group).select(
limitby=(0, limit), orderby = st.next_run_time)
limitby=(0, limit), orderby=st.next_run_time)
# let's break up the queue evenly among workers
for task in tasks:
x += 1
@@ -1183,8 +1167,6 @@ class Scheduler(MetaScheduler):
status=ASSIGNED,
assigned_worker_name=assigned_wn
)
if not task.task_name:
d['task_name'] = task.function_name
db(
(st.id == task.id) &
(st.status.belongs((QUEUED, ASSIGNED)))
@@ -1204,14 +1186,13 @@ class Scheduler(MetaScheduler):
logger.info('TICKER: tasks are %s', x)
def sleep(self):
"""Calculates the number of seconds to sleep according to worker's
status and `heartbeat` parameter"""
"""Calculate the number of seconds to sleep."""
time.sleep(self.w_stats.sleep)
# should only sleep until next available task
def set_worker_status(self, group_names=None, action=ACTIVE,
exclude=None, limit=None, worker_name=None):
"""Internal function to set worker's status"""
"""Internal function to set worker's status."""
ws = self.db.scheduler_worker
if not group_names:
group_names = self.group_names
@@ -1235,10 +1216,12 @@ class Scheduler(MetaScheduler):
self.db(ws.id.belongs(workers)).update(status=action)
def disable(self, group_names=None, limit=None, worker_name=None):
"""Sets DISABLED on the workers processing `group_names` tasks.
"""Set DISABLED on the workers processing `group_names` tasks.
A DISABLED worker will be kept alive but it won't be able to process
any waiting tasks, essentially putting it to sleep.
By default, all group_names of Scheduler's instantation are selected"""
By default, all group_names of Scheduler's instantation are selected
"""
self.set_worker_status(
group_names=group_names,
action=DISABLED,
@@ -1283,8 +1266,9 @@ class Scheduler(MetaScheduler):
pvars: "raw" kwargs to be passed to the function. Automatically
jsonified
kwargs: all the parameters available (basically, every
`scheduler_task` column). If args and vars are here, they should
be jsonified already, and they will override pargs and pvars
`scheduler_task` column). If args and vars are here, they
should be jsonified already, and they will override pargs
and pvars
Returns:
a dict just as a normal validate_and_insert(), plus a uuid key
+38 -20
View File
@@ -677,7 +677,7 @@ class AutocompleteWidget(object):
def callback(self):
if self.keyword in self.request.vars:
field = self.fields[0]
if type(field) is FieldVirtual:
if type(field) is Field.Virtual:
records = []
table_rows = self.db(self.db[field.tablename]).select(orderby=self.orderby)
count = 0
@@ -741,7 +741,7 @@ class AutocompleteWidget(object):
del attr['requires']
attr['_name'] = key2
value = attr['value']
if type(self.fields[0]) is FieldVirtual:
if type(self.fields[0]) is Field.Virtual:
record = None
table_rows = self.db(self.db[self.fields[0].tablename]).select(orderby=self.orderby)
for row in table_rows:
@@ -901,6 +901,7 @@ def formstyle_bootstrap3_stacked(form, fields):
elif controls['_type'] == 'checkbox':
label['_for'] = None
label.insert(0, controls)
label.insert(0, ' ')
_controls = DIV(label, _help, _class="checkbox")
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
@@ -950,6 +951,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
elif controls['_type'] == 'checkbox':
label['_for'] = None
label.insert(0, controls)
label.insert(1, ' ')
_controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class))
label = ''
@@ -1895,33 +1897,39 @@ class SQLFORM(FORM):
else:
field_type = field.type
operators = SELECT(*[OPTION(T(option), _value=option) for option in options], _class='form-control')
operators = SELECT(*[OPTION(T(option), _value=option) for option in options],
_class='form-control')
_id = "%s_%s" % (value_id, name)
if field_type in ['boolean', 'double', 'time', 'integer']:
widget_ = SQLFORM.widgets[field_type]
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control')
elif field_type == 'date':
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
widget_ = SQLFORM.widgets.date
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control',
**iso_format)
elif field_type == 'datetime':
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif (field_type.startswith('reference ') or
field_type.startswith('list:reference ')) and \
hasattr(field.requires, 'options') or \
hasattr(field.requires, 'options'):
value_input = widget_.widget(field, field.default, _id=_id,
_class=widget_._class + ' form-control',
**iso_format)
elif hasattr(field.requires, 'options'):
value_input = SELECT(
*[OPTION(v, _value=k)
for k, v in field.requires.options()],
_class='form-control',
**dict(_id=_id))
elif field_type.startswith('reference ') or \
field_type.startswith('list:integer') or \
field_type.startswith('list:reference '):
elif (field_type.startswith('integer') or
field_type.startswith('reference ') or
field_type.startswith('list:integer') or
field_type.startswith('list:reference ')):
widget_ = SQLFORM.widgets.integer
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
value_input = widget_.widget(
field, field.default, _id=_id,
_class=widget_._class + ' form-control')
else:
value_input = INPUT(
_type='text', _id=_id,
@@ -2183,7 +2191,7 @@ class SQLFORM(FORM):
buttonurl=url(args=[]), callback=None,
delete=None, trap=True, noconfirm=None, title=None):
if showbuttontext:
return A(SPAN(_class=ui.get(buttonclass)),
return A(SPAN(_class=ui.get(buttonclass)), CAT(' '),
SPAN(T(buttontext), _title=title or T(buttontext),
_class=ui.get('buttontext')),
_href=buttonurl,
@@ -2204,15 +2212,12 @@ class SQLFORM(FORM):
dbset = db(query, ignore_common_filters=ignore_common_filters)
tablenames = db._adapter.tables(dbset.query)
print dbset.query
print tablenames
if left is not None:
if not isinstance(left, (list, tuple)):
left = [left]
for join in left:
tablenames += db._adapter.tables(join)
tables = [db[tablename] for tablename in tablenames]
print tables
if fields:
# add missing tablename to virtual fields
for table in tables:
@@ -2744,7 +2749,11 @@ class SQLFORM(FORM):
if field.type == 'blob':
continue
if isinstance(field, Field.Virtual) and field.tablename in row:
value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
try:
# fast path, works for joins
value = row[field.tablename][field.name]
except KeyError:
value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
else:
value = row[str(field)]
maxlength = maxtextlengths.get(str(field), maxtextlength)
@@ -3034,7 +3043,16 @@ class SQLFORM(FORM):
query = query & constraints[table._tablename]
if isinstance(links, dict):
links = links.get(table._tablename, [])
for key in 'columns,orderby,searchable,sortable,paginate,deletable,editable,details,selectable,create,fields'.split(','):
for key in ('fields', 'field_id', 'left', 'headers', 'orderby', 'groupby', 'searchable',
'sortable', 'paginate', 'deletable', 'editable', 'details', 'selectable',
'create', 'csv', 'links', 'links_in_grid', 'upload', 'maxtextlengths',
'maxtextlength', 'onvalidation', 'onfailure', 'oncreate', 'onupdate',
'ondelete', 'sorter_icons', 'ui', 'showbuttontext', '_class', 'formname',
'search_widget', 'advanced_search', 'ignore_rw', 'formstyle', 'exportclasses',
'formargs', 'createargs', 'editargs', 'viewargs', 'selectable_submit_button',
'buttons_placement', 'links_placement', 'noconfirm', 'cache_count', 'client_side_delete',
'ignore_common_filters', 'auto_pagination', 'use_cursor'
):
if isinstance(kwargs.get(key, None), dict):
if table._tablename in kwargs[key]:
kwargs[key] = kwargs[key][table._tablename]
+2
View File
@@ -21,6 +21,8 @@ from test_contribs import *
from test_web import *
from test_dal import *
from test_tools import *
from test_appadmin import *
from test_scheduler import *
if sys.version[:3] == '2.7':
from test_old_doctests import *
+175
View File
@@ -0,0 +1,175 @@
#!/bin/python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.sqlhtml
"""
import os
import sys
if sys.version < "2.7":
import unittest2 as unittest
else:
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from compileapp import run_controller_in, run_view_in
from languages import translator
from gluon.storage import Storage, List
import gluon.fileutils
from gluon.dal import DAL, Field, Table
from gluon.http import HTTP
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
try:
import json
except ImportError:
from gluon.contrib import simplejson as json
def fake_check_credentials(foo):
return True
class TestAppAdmin(unittest.TestCase):
def setUp(self):
from gluon.globals import Request, Response, Session, current
from gluon.html import A, DIV, FORM, MENU, TABLE, TR, INPUT, URL, XML
from gluon.validators import IS_NOT_EMPTY
from compileapp import LOAD
from gluon.http import HTTP, redirect
from gluon.tools import Auth
from gluon.sql import SQLDB
from gluon.sqlhtml import SQLTABLE, SQLFORM
self.original_check_credentials = gluon.fileutils.check_credentials
gluon.fileutils.check_credentials = fake_check_credentials
request = Request(env={})
request.application = 'welcome'
request.controller = 'appadmin'
request.function = self._testMethodName.split('_')[1]
request.folder = 'applications/welcome'
request.env.http_host = '127.0.0.1:8000'
request.env.remote_addr = '127.0.0.1'
response = Response()
session = Session()
T = translator('', 'en')
session.connect(request, response)
current.request = request
current.response = response
current.session = session
current.T = T
db = DAL(DEFAULT_URI, check_reserved=['all'])
auth = Auth(db)
auth.define_tables(username=True, signature=False)
db.define_table('t0', Field('tt'), auth.signature)
# Create a user
db.auth_user.insert(first_name='Bart',
last_name='Simpson',
username='user1',
email='user1@test.com',
password='password_123',
registration_key=None,
registration_id=None)
self.env = locals()
def tearDown(self):
gluon.fileutils.check_credentials = self.original_check_credentials
def run_function(self):
return run_controller_in(self.env['request'].controller, self.env['request'].function, self.env)
def run_view(self):
return run_view_in(self.env)
def test_index(self):
result = self.run_function()
self.assertTrue('db' in result['databases'])
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_select(self):
request = self.env['request']
request.args = List(['db'])
request.env.query_string = 'query=db.auth_user.id>0'
result = self.run_function()
self.assertTrue('table' in result and 'query' in result)
self.assertTrue(result['table'] == 'auth_user')
self.assertTrue(result['query'] == 'db.auth_user.id>0')
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_insert(self):
request = self.env['request']
request.args = List(['db', 'auth_user'])
result = self.run_function()
self.assertTrue('table' in result)
self.assertTrue('form' in result)
self.assertTrue(str(result['table']) is 'auth_user')
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
def test_insert_submit(self):
request = self.env['request']
request.args = List(['db', 'auth_user'])
form = self.run_function()['form']
hidden_fields = form.hidden_fields()
data = {}
data['_formkey'] = hidden_fields.element('input', _name='_formkey')['_value']
data['_formname'] = hidden_fields.element('input', _name='_formname')['_value']
data['first_name'] = 'Lisa'
data['last_name'] = 'Simpson'
data['username'] = 'lisasimpson'
data['password'] = 'password_123'
data['email'] = 'lisa@example.com'
request._vars = data
result = self.run_function()
self.env.update(result)
try:
self.run_view()
except Exception as e:
print e.message
self.fail('Could not make the view')
db = self.env['db']
lisa_record = db(db.auth_user.username == 'lisasimpson').select().first()
self.assertIsNotNone(lisa_record)
del data['_formkey']
del data['_formname']
del data['password']
for key in data:
self.assertEqual(data[key], lisa_record[key])
def test_update_submit(self):
request = self.env['request']
request.args = List(['db', 'auth_user', '1'])
form = self.run_function()['form']
hidden_fields = form.hidden_fields()
data = {}
data['_formkey'] = hidden_fields.element('input', _name='_formkey')['_value']
data['_formname'] = hidden_fields.element('input', _name='_formname')['_value']
for element in form.elements('input'):
data[element['_name']] = element['_value']
data['email'] = 'user1@example.com'
data['id'] = '1'
request._vars = data
self.assertRaises(HTTP, self.run_function)
if __name__ == '__main__':
unittest.main()
+20 -14
View File
@@ -37,10 +37,11 @@ def tearDownModule():
pass
class TestCache(unittest.TestCase):
def testCacheInRam(self):
# TODO: test_CacheAbstract(self):
def test_CacheInRam(self):
# defaults to mode='http'
cache = CacheInRam()
@@ -53,22 +54,21 @@ class TestCache(unittest.TestCase):
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test singleton behaviour
# test singleton behaviour
cache = CacheInRam()
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test key deletion
# test key deletion
cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5)
#test increment
# test increment
self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1)
def testCacheOnDisk(self):
def test_CacheOnDisk(self):
# defaults to mode='http'
s = Storage({'application': 'admin',
@@ -83,30 +83,36 @@ class TestCache(unittest.TestCase):
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test singleton behaviour
# test singleton behaviour
cache = CacheOnDisk(s)
cache.clear()
self.assertEqual(cache('a', lambda: 3, 100), 3)
self.assertEqual(cache('a', lambda: 4, 0), 4)
#test key deletion
# test key deletion
cache('a', None)
self.assertEqual(cache('a', lambda: 5, 100), 5)
#test increment
# test increment
self.assertEqual(cache.increment('a'), 6)
self.assertEqual(cache('a', lambda: 1, 100), 6)
cache.increment('b')
self.assertEqual(cache('b', lambda: 'x', 100), 1)
def testCacheWithPrefix(self):
# TODO: def test_CacheAction(self):
# TODO: def test_Cache(self):
# TODO: def test_lazy_cache(self):
def test_CacheWithPrefix(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)
prefix = cache.with_prefix(cache.ram,'prefix')
prefix = cache.with_prefix(cache.ram, 'prefix')
self.assertEqual(prefix('a', lambda: 1, 0), 1)
self.assertEqual(prefix('a', lambda: 2, 100), 1)
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
def testRegex(self):
def test_Regex(self):
cache = CacheInRam()
self.assertEqual(cache('a1', lambda: 1, 0), 1)
self.assertEqual(cache('a2', lambda: 2, 100), 2)
@@ -114,7 +120,7 @@ class TestCache(unittest.TestCase):
self.assertEqual(cache('a1', lambda: 2, 0), 2)
self.assertEqual(cache('a2', lambda: 3, 100), 3)
def testDALcache(self):
def test_DALcache(self):
s = Storage({'application': 'admin',
'folder': 'applications/admin'})
cache = Cache(s)
+8 -6
View File
@@ -106,16 +106,18 @@ class TestDALAdapters(unittest.TestCase):
def test_mysql(self):
if os.environ.get('APPVEYOR'):
return
os.environ["DB"] = "mysql://root:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if os.environ.get('TRAVIS'):
os.environ["DB"] = "mysql://root:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
def test_pg8000(self):
if os.environ.get('APPVEYOR'):
return
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if os.environ.get('TRAVIS'):
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if __name__ == '__main__':
+11 -5
View File
@@ -12,13 +12,19 @@ from fileutils import parse_version
class TestFileUtils(unittest.TestCase):
def testParseVersion(self):
rtn = parse_version('Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26')
self.assertEqual(rtn, (1, 99, 0, 'rc.1', datetime.datetime(2011, 9, 19, 8, 23, 26)))
rtn = parse_version('Version 2.9.11-stable+timestamp.2014.09.15.18.31.17')
self.assertEqual(rtn, (2, 9, 11, 'stable', datetime.datetime(2014, 9, 15, 18, 31, 17)))
def test_parse_version(self):
# Legacy
rtn = parse_version('Version 1.99.0 (2011-09-19 08:23:26)')
self.assertEqual(rtn, (1, 99, 0, 'dev', datetime.datetime(2011, 9, 19, 8, 23, 26)))
# Semantic
rtn = parse_version('Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26')
self.assertEqual(rtn, (1, 99, 0, 'rc.1', datetime.datetime(2011, 9, 19, 8, 23, 26)))
# Semantic Stable
rtn = parse_version('Version 2.9.11-stable+timestamp.2014.09.15.18.31.17')
self.assertEqual(rtn, (2, 9, 11, 'stable', datetime.datetime(2014, 9, 15, 18, 31, 17)))
# Semantic Beta
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26')
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
if __name__ == '__main__':
+579 -319
View File
@@ -12,314 +12,30 @@ fix_sys_path(__file__)
from html import *
from html import verifyURL
from html import truncate_string
from storage import Storage
from html import XML_pickle, XML_unpickle
from html import TAG_pickler, TAG_unpickler
class TestBareHelpers(unittest.TestCase):
def testBR(self):
self.assertEqual(BR(_a='1', _b='2').xml(), '<br a="1" b="2" />')
# xmlescape() = covered by other tests
def testEMBED(self):
self.assertEqual(EMBED(_a='1', _b='2').xml(),
'<embed a="1" b="2" />')
# TODO: def test_call_as_list(self):
def testHR(self):
self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />')
def test_truncate_string(self):
# Ascii text
self.assertEqual(truncate_string('Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
length=30), 'Lorem ipsum dolor sit amet,...')
self.assertEqual(truncate_string('Short text shorter than the length parameter.', length=100),
'Short text shorter than the length parameter.')
# French text
self.assertEqual(truncate_string('Un texte en français avec des accents et des caractères bizarre.', length=30),
'Un texte en français avec d...')
def testIMG(self):
self.assertEqual(IMG(_a='1', _b='2').xml(),
'<img a="1" b="2" />')
def testINPUT(self):
self.assertEqual(INPUT(_a='1', _b='2').xml(),
'<input a="1" b="2" type="text" />')
def testLINK(self):
self.assertEqual(LINK(_a='1', _b='2').xml(),
'<link a="1" b="2" />')
def testMETA(self):
self.assertEqual(META(_a='1', _b='2').xml(),
'<meta a="1" b="2" />')
def testA(self):
self.assertEqual(
A('<>', _a='1', _b='2').xml(),
'<a a="1" b="2">&lt;&gt;</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', delete='tr').xml(),
'<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>'
)
self.assertEqual(
A('a', _id='b', target='<self>').xml(),
'<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>'
)
self.assertEqual(
A('a', component='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>'
)
self.assertEqual(
A('a', _id='b', callback='c', noconfirm=True).xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', cid='b', _disable_with='processing...').xml(),
'<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
'<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
def testB(self):
self.assertEqual(B('<>', _a='1', _b='2').xml(),
'<b a="1" b="2">&lt;&gt;</b>')
def testBODY(self):
self.assertEqual(BODY('<>', _a='1', _b='2').xml(),
'<body a="1" b="2">&lt;&gt;</body>')
def testCENTER(self):
self.assertEqual(CENTER('<>', _a='1', _b='2').xml(),
'<center a="1" b="2">&lt;&gt;</center>')
def testDIV(self):
self.assertEqual(DIV('<>', _a='1', _b='2').xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# attributes can be updated like in a dict
div = DIV('<>', _a='1')
div['_b'] = '2'
self.assertEqual(div.xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# also with a mapping
div.update(_b=2, _c=3)
self.assertEqual(div.xml(),
'<div a="1" b="2" c="3">&lt;&gt;</div>')
# length of the DIV is the number of components
self.assertEqual(len(DIV('a', 'bc')), 2)
# also if empty, DIV is True in a boolean evaluation
self.assertTrue(True if DIV() else False)
# parent and siblings
a = DIV(SPAN('a'), DIV('b'))
s = a.element('span')
d = s.parent
d['_class'] = 'abc'
self.assertEqual(a.xml(), '<div class="abc"><span>a</span><div>b</div></div>')
self.assertEqual([el.xml() for el in s.siblings()], ['<div>b</div>'])
self.assertEqual(s.sibling().xml(), '<div>b</div>')
self.assertEqual(s.siblings('a'), [])
def testEM(self):
self.assertEqual(EM('<>', _a='1', _b='2').xml(),
'<em a="1" b="2">&lt;&gt;</em>')
def testFIELDSET(self):
self.assertEqual(FIELDSET('<>', _a='1', _b='2').xml(),
'<fieldset a="1" b="2">&lt;&gt;</fieldset>')
def testFORM(self):
self.assertEqual(FORM('<>', _a='1', _b='2').xml(),
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post">&lt;&gt;</form>')
def testH1(self):
self.assertEqual(H1('<>', _a='1', _b='2').xml(),
'<h1 a="1" b="2">&lt;&gt;</h1>')
def testH2(self):
self.assertEqual(H2('<>', _a='1', _b='2').xml(),
'<h2 a="1" b="2">&lt;&gt;</h2>')
def testH3(self):
self.assertEqual(H3('<>', _a='1', _b='2').xml(),
'<h3 a="1" b="2">&lt;&gt;</h3>')
def testH4(self):
self.assertEqual(H4('<>', _a='1', _b='2').xml(),
'<h4 a="1" b="2">&lt;&gt;</h4>')
def testH5(self):
self.assertEqual(H5('<>', _a='1', _b='2').xml(),
'<h5 a="1" b="2">&lt;&gt;</h5>')
def testH6(self):
self.assertEqual(H6('<>', _a='1', _b='2').xml(),
'<h6 a="1" b="2">&lt;&gt;</h6>')
def testHEAD(self):
self.assertEqual(HEAD('<>', _a='1', _b='2').xml(),
'<head a="1" b="2">&lt;&gt;</head>')
def testHTML(self):
self.assertEqual(HTML('<>', _a='1', _b='2').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
def testIFRAME(self):
self.assertEqual(IFRAME('<>', _a='1', _b='2').xml(),
'<iframe a="1" b="2">&lt;&gt;</iframe>')
def testLABEL(self):
self.assertEqual(LABEL('<>', _a='1', _b='2').xml(),
'<label a="1" b="2">&lt;&gt;</label>')
def testLI(self):
self.assertEqual(LI('<>', _a='1', _b='2').xml(),
'<li a="1" b="2">&lt;&gt;</li>')
def testOBJECT(self):
self.assertEqual(OBJECT('<>', _a='1', _b='2').xml(),
'<object a="1" b="2">&lt;&gt;</object>')
def testOL(self):
self.assertEqual(OL('<>', _a='1', _b='2').xml(),
'<ol a="1" b="2"><li>&lt;&gt;</li></ol>')
def testOPTION(self):
self.assertEqual(OPTION('<>', _a='1', _b='2').xml(),
'<option a="1" b="2" value="&lt;&gt;">&lt;&gt;' +
'</option>')
def testP(self):
self.assertEqual(P('<>', _a='1', _b='2').xml(),
'<p a="1" b="2">&lt;&gt;</p>')
# test cr2br
self.assertEqual(P('a\nb').xml(), '<p>a\nb</p>')
self.assertEqual(P('a\nb', cr2br=True).xml(), '<p>a<br />b</p>')
def testPRE(self):
self.assertEqual(PRE('<>', _a='1', _b='2').xml(),
'<pre a="1" b="2">&lt;&gt;</pre>')
def testSCRIPT(self):
self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(),
'''<script a="1" b="2"><!--
<>
//--></script>''')
self.assertEqual(SCRIPT('<>').xml(),
'''<script><!--
<>
//--></script>''')
self.assertEqual(SCRIPT().xml(), '<script></script>')
def testSELECT(self):
self.assertEqual(SELECT('<>', _a='1', _b='2').xml(),
'<select a="1" b="2">' +
'<option value="&lt;&gt;">&lt;&gt;</option></select>')
def testSPAN(self):
self.assertEqual(SPAN('<>', _a='1', _b='2').xml(),
'<span a="1" b="2">&lt;&gt;</span>')
def testSTYLE(self):
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
def testTABLE(self):
self.assertEqual(TABLE('<>', _a='1', _b='2').xml(),
'<table a="1" b="2"><tr><td>&lt;&gt;</td></tr>' +
'</table>')
def testTBODY(self):
self.assertEqual(TBODY('<>', _a='1', _b='2').xml(),
'<tbody a="1" b="2"><tr><td>&lt;&gt;</td></tr></tbody>')
def testTD(self):
self.assertEqual(TD('<>', _a='1', _b='2').xml(),
'<td a="1" b="2">&lt;&gt;</td>')
def testTEXTAREA(self):
self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(),
'<textarea a="1" b="2" cols="40" rows="10">&lt;&gt;' +
'</textarea>')
# override _rows and _cols
self.assertEqual(TEXTAREA('<>', _a='1', _b='2', _rows=5, _cols=20).xml(),
'<textarea a="1" b="2" cols="20" rows="5">&lt;&gt;' +
'</textarea>')
def testTFOOT(self):
self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(),
'<tfoot a="1" b="2"><tr><td>&lt;&gt;</td></tr></tfoot>')
def testTH(self):
self.assertEqual(TH('<>', _a='1', _b='2').xml(),
'<th a="1" b="2">&lt;&gt;</th>')
def testTHEAD(self):
self.assertEqual(THEAD('<>', _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
#self.assertEqual(THEAD(TRHEAD('<>'), _a='1', _b='2').xml(),
# '<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
self.assertEqual(THEAD(TR('<>'), _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><td>&lt;&gt;</td></tr></thead>')
def testTITLE(self):
self.assertEqual(TITLE('<>', _a='1', _b='2').xml(),
'<title a="1" b="2">&lt;&gt;</title>')
def testTR(self):
self.assertEqual(TR('<>', _a='1', _b='2').xml(),
'<tr a="1" b="2"><td>&lt;&gt;</td></tr>')
def testTT(self):
self.assertEqual(TT('<>', _a='1', _b='2').xml(),
'<tt a="1" b="2">&lt;&gt;</tt>')
def testUL(self):
self.assertEqual(UL('<>', _a='1', _b='2').xml(),
'<ul a="1" b="2"><li>&lt;&gt;</li></ul>')
def testXML(self):
# sanitization process
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
'<h1>Hello<a data-hello="world">World</a></h1>')
# with sanitize, data-attributes are not permitted
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True).xml(),
'<h1>HelloWorld</h1>')
# stringify by default
self.assertEqual(XML(1.3), '1.3')
self.assertEqual(XML(u'<div>è</div>').xml(), '<div>\xc3\xa8</div>')
# you can calc len on the class, that equals the xml() and the str()
self.assertEqual(len(XML('1.3')), len('1.3'))
self.assertEqual(len(XML('1.3').xml()), len('1.3'))
self.assertEqual(len(str(XML('1.3'))), len('1.3'))
# you can concatenate them to strings (check for __add__ and __radd__ methods)
self.assertEqual(XML('a') + 'b', 'ab')
self.assertEqual(XML('a') + XML('b'), 'ab')
self.assertEqual('a' + XML('b'), 'ab')
# you can compare them
self.assertEqual(XML('a') == XML('a'), True)
# beware that the comparison is made on the XML repr
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
XML('<h1>HelloWorld</h1>'))
#bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
def testTAG(self):
self.assertEqual(TAG.first(TAG.second('test'), _key=3).xml(),
'<first key="3"><second>test</second></first>')
# ending in underscore "triggers" <input /> style
self.assertEqual(TAG.first_(TAG.second('test'), _key=3).xml(),
'<first key="3" />')
def testStaticURL(self):
def test_StaticURL(self):
# test response.static_version coupled with response.static_version_urls
self.assertEqual(URL('a', 'c', 'f'), '/a/c/f')
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/design.css')
@@ -331,62 +47,67 @@ class TestBareHelpers(unittest.TestCase):
response.static_version_urls = True
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/_1.2.3/design.css')
def testURL(self):
def test_URL(self):
self.assertEqual(URL('a', 'c', 'f', args='1'), '/a/c/f/1')
self.assertEqual(URL('a', 'c', 'f', args=('1', '2')), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', 'f', args=['1', '2']), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', '/f'), '/a/c/f')
self.assertEqual(URL('a', 'c', 'f.json'), '/a/c/f.json')
self.assertRaises(SyntaxError, URL, *['a'])
request = Storage()
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.env = {}
from globals import current
from globals import current # Can't be moved with other import
current.request = request
must_return = '/a/c/f'
self.assertEqual(URL(), must_return)
self.assertEqual(URL('f'), must_return)
self.assertEqual(URL('c', 'f'), must_return)
self.assertEqual(URL('a', 'c', 'f'), must_return)
self.assertEqual(URL('a', 'c', 'f', extension='json'), '/a/c/f.json')
def weird():
pass
self.assertEqual(URL('a', 'c', weird), '/a/c/weird')
self.assertRaises(SyntaxError, URL, *['a', 'c', 1])
# test signature
rtn = URL(
a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2}, anchor='1', hmac_key='key'
)
rtn = URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2}, anchor='1', hmac_key='key')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1')
# test _signature exclusion
rtn = URL(
a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2, '_signature': 'abc'},
anchor='1', hmac_key='key'
)
rtn = URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2, '_signature': 'abc'},
anchor='1', hmac_key='key')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1')
# emulate user_signature
current.session = Storage(auth=Storage(hmac_key='key'))
self.assertEqual(URL(user_signature=True), '/a/c/f?_signature=c4aed53c08cff08f369dbf8b5ba51889430cf2c2')
# hash_vars combination
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars=True)
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars=True)
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars=False)
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars=False)
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=0b5a0702039992aad23c82794b8496e5dcd59a5b')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars=['p'])
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars=['p'])
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars='p')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars='p')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a')
# test url_encode
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'maï': (1, 3), 'lié': 2}, url_encode=False)
self.assertEqual(rtn, '/a/c/f/x/y/z?li\xc3\xa9=2&ma\xc3\xaf=1&ma\xc3\xaf=3')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'maï': (1, 3), 'lié': 2}, url_encode=True)
self.assertEqual(rtn, '/a/c/f/x/y/z?li%C3%A9=2&ma%C3%AF=1&ma%C3%AF=3')
# test CRLF detection
self.assertRaises(SyntaxError, URL, *['a\n', 'c', 'f'])
self.assertRaises(SyntaxError, URL, *['a\r', 'c', 'f'])
def testverifyURL(self):
def test_verifyURL(self):
r = Storage()
r.application = 'a'
r.controller = 'c'
@@ -429,12 +150,551 @@ class TestBareHelpers(unittest.TestCase):
rtn = verifyURL(r, user_signature=True)
self.assertEqual(rtn, True)
# TODO: def test_XmlComponent(self):
def test_XML(self):
# sanitization process
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
'<h1>Hello<a data-hello="world">World</a></h1>')
# with sanitize, data-attributes are not permitted
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True).xml(),
'<h1>HelloWorld</h1>')
# stringify by default
self.assertEqual(XML(1.3), '1.3')
self.assertEqual(XML(u'<div>è</div>').xml(), '<div>\xc3\xa8</div>')
# you can calc len on the class, that equals the xml() and the str()
self.assertEqual(len(XML('1.3')), len('1.3'))
self.assertEqual(len(XML('1.3').xml()), len('1.3'))
self.assertEqual(len(str(XML('1.3'))), len('1.3'))
# you can concatenate them to strings (check for __add__ and __radd__ methods)
self.assertEqual(XML('a') + 'b', 'ab')
self.assertEqual(XML('a') + XML('b'), 'ab')
self.assertEqual('a' + XML('b'), 'ab')
# you can compare them
self.assertEqual(XML('a') == XML('a'), True)
# beware that the comparison is made on the XML repr
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
XML('<h1>HelloWorld</h1>'))
# bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
# basic flatten test
self.assertEqual(XML('<p>Test</p>').flatten(), '<p>Test</p>')
self.assertEqual(XML('<p>Test</p>').flatten(render=lambda text, tag, attr: text), '<p>Test</p>')
def test_XML_pickle_unpickle(self):
# weird test
self.assertEqual(XML_unpickle(XML_pickle('data to be pickle')[1][0]), 'data to be pickle')
def test_DIV(self):
# Empty DIV()
self.assertEqual(DIV().xml(), '<div></div>')
self.assertEqual(DIV('<>', _a='1', _b='2').xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# attributes can be updated like in a dict
div = DIV('<>', _a='1')
div['_b'] = '2'
self.assertEqual(div.xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# also with a mapping
div.update(_b=2, _c=3)
self.assertEqual(div.xml(),
'<div a="1" b="2" c="3">&lt;&gt;</div>')
# length of the DIV is the number of components
self.assertEqual(len(DIV('a', 'bc')), 2)
# also if empty, DIV is True in a boolean evaluation
self.assertTrue(True if DIV() else False)
# parent and siblings
a = DIV(SPAN('a'), DIV('b'))
s = a.element('span')
d = s.parent
d['_class'] = 'abc'
self.assertEqual(a.xml(), '<div class="abc"><span>a</span><div>b</div></div>')
self.assertEqual([el.xml() for el in s.siblings()], ['<div>b</div>'])
self.assertEqual(s.sibling().xml(), '<div>b</div>')
# siblings with wrong args
self.assertEqual(s.siblings('a'), [])
# siblings with good args
self.assertEqual(s.siblings('div')[0].xml(), '<div>b</div>')
# Check for siblings with wrong kargs and value
self.assertEqual(s.siblings(a='d'), [])
# Check for siblings with good kargs and value
# Can't figure this one out what is a right value here??
# Commented for now...
# self.assertEqual(s.siblings(div='<div>b</div>'), ???)
# No other sibling should return None
self.assertEqual(DIV(P('First element')).element('p').sibling(), None)
# --------------------------------------------------------------------------------------------------------------
# This use unicode to hit xmlescape() line :
# """
# elif isinstance(data, unicode):
# data = data.encode('utf8', 'xmlcharrefreplace')
# """
self.assertEqual(DIV(u'Texte en français avec des caractères accentués...').xml(),
'<div>Texte en fran\xc3\xa7ais avec des caract\xc3\xa8res accentu\xc3\xa9s...</div>')
# --------------------------------------------------------------------------------------------------------------
self.assertEqual(DIV('Test with an ID', _id='id-of-the-element').xml(),
'<div id="id-of-the-element">Test with an ID</div>')
self.assertEqual(DIV().element('p'), None)
# Corner case for raise coverage of one line
# I think such assert fail cause of python 2.6
# Work under python 2.7
# with self.assertRaises(SyntaxError) as cm:
# DIV(BR('<>')).xml()
# self.assertEqual(cm.exception[0], '<br/> tags cannot have components')
# test .get('attrib')
self.assertEqual(DIV('<p>Test</p>', _class="class_test").get('_class'), 'class_test')
def test_CAT(self):
# Empty CAT()
self.assertEqual(CAT().xml(), '')
# CAT('')
self.assertEqual(CAT('').xml(), '')
# CAT(' ')
self.assertEqual(CAT(' ').xml(), ' ')
def test_TAG_pickler_unpickler(self):
# weird test
self.assertEqual(TAG_unpickler(TAG_pickler(TAG.div('data to be pickle'))[1][0]).xml(),
'<div>data to be pickle</div>')
def test_TAG(self):
self.assertEqual(TAG.first(TAG.second('test'), _key=3).xml(),
'<first key="3"><second>test</second></first>')
# ending in underscore "triggers" <input /> style
self.assertEqual(TAG.first_(TAG.second('test'), _key=3).xml(),
'<first key="3" />')
# unicode test for TAG
self.assertEqual(TAG.div(u'Texte en français avec des caractères accentués...').xml(),
'<div>Texte en fran\xc3\xa7ais avec des caract\xc3\xa8res accentu\xc3\xa9s...</div>')
def test_HTML(self):
self.assertEqual(HTML('<>', _a='1', _b='2').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='strict').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='transitional').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='frameset').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='html5').xml(),
'<!DOCTYPE HTML>\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='').xml(),
'<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='CustomDocType').xml(),
'CustomDocType\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
def test_XHTML(self):
# Empty XHTML test
self.assertEqual(XHTML().xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>')
# Not Empty XHTML test
self.assertEqual(XHTML('<>', _a='1', _b='2').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='strict').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='transitional').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='frameset').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='xmlns').xml(),
'xmlns\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', _xmlns='xmlns').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
def test_HEAD(self):
self.assertEqual(HEAD('<>', _a='1', _b='2').xml(),
'<head a="1" b="2">&lt;&gt;</head>')
def test_TITLE(self):
self.assertEqual(TITLE('<>', _a='1', _b='2').xml(),
'<title a="1" b="2">&lt;&gt;</title>')
def test_META(self):
self.assertEqual(META(_a='1', _b='2').xml(),
'<meta a="1" b="2" />')
def test_LINK(self):
self.assertEqual(LINK(_a='1', _b='2').xml(),
'<link a="1" b="2" />')
def test_SCRIPT(self):
self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(),
'''<script a="1" b="2"><!--
<>
//--></script>''')
self.assertEqual(SCRIPT('<>').xml(),
'''<script><!--
<>
//--></script>''')
self.assertEqual(SCRIPT().xml(), '<script></script>')
def test_STYLE(self):
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
# Try to hit : return DIV.xml(self)
self.assertEqual(STYLE().xml(), '<style></style>')
def test_IMG(self):
self.assertEqual(IMG(_a='1', _b='2').xml(),
'<img a="1" b="2" />')
def test_SPAN(self):
self.assertEqual(SPAN('<>', _a='1', _b='2').xml(),
'<span a="1" b="2">&lt;&gt;</span>')
def test_BODY(self):
self.assertEqual(BODY('<>', _a='1', _b='2').xml(),
'<body a="1" b="2">&lt;&gt;</body>')
def test_H1(self):
self.assertEqual(H1('<>', _a='1', _b='2').xml(),
'<h1 a="1" b="2">&lt;&gt;</h1>')
def test_H2(self):
self.assertEqual(H2('<>', _a='1', _b='2').xml(),
'<h2 a="1" b="2">&lt;&gt;</h2>')
def test_H3(self):
self.assertEqual(H3('<>', _a='1', _b='2').xml(),
'<h3 a="1" b="2">&lt;&gt;</h3>')
def test_H4(self):
self.assertEqual(H4('<>', _a='1', _b='2').xml(),
'<h4 a="1" b="2">&lt;&gt;</h4>')
def test_H5(self):
self.assertEqual(H5('<>', _a='1', _b='2').xml(),
'<h5 a="1" b="2">&lt;&gt;</h5>')
def test_H6(self):
self.assertEqual(H6('<>', _a='1', _b='2').xml(),
'<h6 a="1" b="2">&lt;&gt;</h6>')
def test_P(self):
self.assertEqual(P('<>', _a='1', _b='2').xml(),
'<p a="1" b="2">&lt;&gt;</p>')
# test cr2br
self.assertEqual(P('a\nb').xml(), '<p>a\nb</p>')
self.assertEqual(P('a\nb', cr2br=True).xml(), '<p>a<br />b</p>')
def test_STRONG(self):
self.assertEqual(STRONG('<>', _a='1', _b='2').xml(),
'<strong a="1" b="2">&lt;&gt;</strong>')
def test_B(self):
self.assertEqual(B('<>', _a='1', _b='2').xml(),
'<b a="1" b="2">&lt;&gt;</b>')
def test_BR(self):
# empty BR()
self.assertEqual(BR().xml(), '<br />')
self.assertEqual(BR(_a='1', _b='2').xml(), '<br a="1" b="2" />')
def test_HR(self):
self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />')
def test_A(self):
self.assertEqual(A('<>', _a='1', _b='2').xml(),
'<a a="1" b="2">&lt;&gt;</a>')
self.assertEqual(A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>')
self.assertEqual(A('a', callback='b', _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>')
# Callback with no id trigger web2py_uuid() call
from html import web2pyHTMLParser
a = A('a', callback='b').xml()
for tag in web2pyHTMLParser(a).tree.elements('a'):
uuid_generated = tag.attributes['_id']
self.assertEqual(a,
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="{id}">a</a>'.format(id=uuid_generated))
self.assertEqual(A('a', delete='tr').xml(),
'<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>')
self.assertEqual(A('a', _id='b', target='<self>').xml(),
'<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>')
self.assertEqual(A('a', component='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>')
self.assertEqual(A('a', _id='b', callback='c', noconfirm=True).xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>')
self.assertEqual(A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>')
self.assertEqual(A('a', cid='b', _disable_with='processing...').xml(),
'<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>')
self.assertEqual(A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>')
self.assertEqual(A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
'<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>')
def test_BUTTON(self):
self.assertEqual(BUTTON('test', _type='button').xml(),
'<button type="button">test</button>')
def test_EM(self):
self.assertEqual(EM('<>', _a='1', _b='2').xml(),
'<em a="1" b="2">&lt;&gt;</em>')
def test_EMBED(self):
self.assertEqual(EMBED(_a='1', _b='2').xml(),
'<embed a="1" b="2" />')
def test_TT(self):
self.assertEqual(TT('<>', _a='1', _b='2').xml(),
'<tt a="1" b="2">&lt;&gt;</tt>')
def test_PRE(self):
self.assertEqual(PRE('<>', _a='1', _b='2').xml(),
'<pre a="1" b="2">&lt;&gt;</pre>')
def test_CENTER(self):
self.assertEqual(CENTER('<>', _a='1', _b='2').xml(),
'<center a="1" b="2">&lt;&gt;</center>')
def test_CODE(self):
self.assertEqual(CODE("print 'hello world'",
language='python',
link=None,
counter=1,
styles={},
highlight_line=None).xml(),
'<table><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="\n font-size: 11px;\n font-family: Bitstream Vera Sans Mono,monospace;\n background-color: transparent;\n margin: 0;\n padding: 5px;\n border: none;\n color: #A0A0A0;\n">1.</pre></td><td><pre style="\n font-size: 11px;\n font-family: Bitstream Vera Sans Mono,monospace;\n background-color: transparent;\n margin: 0;\n padding: 5px;\n border: none;\n overflow: auto;\n white-space: pre !important;\n"><span style="color:#185369; font-weight: bold">print </span><span style="color: #FF9966">\'hello world\'</span></pre></td></tr></table>')
def test_LABEL(self):
self.assertEqual(LABEL('<>', _a='1', _b='2').xml(),
'<label a="1" b="2">&lt;&gt;</label>')
def test_LI(self):
self.assertEqual(LI('<>', _a='1', _b='2').xml(),
'<li a="1" b="2">&lt;&gt;</li>')
def test_UL(self):
self.assertEqual(UL('<>', _a='1', _b='2').xml(),
'<ul a="1" b="2"><li>&lt;&gt;</li></ul>')
def test_OL(self):
self.assertEqual(OL('<>', _a='1', _b='2').xml(),
'<ol a="1" b="2"><li>&lt;&gt;</li></ol>')
def test_TD(self):
self.assertEqual(TD('<>', _a='1', _b='2').xml(),
'<td a="1" b="2">&lt;&gt;</td>')
def test_TH(self):
self.assertEqual(TH('<>', _a='1', _b='2').xml(),
'<th a="1" b="2">&lt;&gt;</th>')
def test_TR(self):
self.assertEqual(TR('<>', _a='1', _b='2').xml(),
'<tr a="1" b="2"><td>&lt;&gt;</td></tr>')
def test_THEAD(self):
self.assertEqual(THEAD('<>', _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
# self.assertEqual(THEAD(TRHEAD('<>'), _a='1', _b='2').xml(),
# '<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
self.assertEqual(THEAD(TR('<>'), _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><td>&lt;&gt;</td></tr></thead>')
def test_TBODY(self):
self.assertEqual(TBODY('<>', _a='1', _b='2').xml(),
'<tbody a="1" b="2"><tr><td>&lt;&gt;</td></tr></tbody>')
def test_TFOOT(self):
self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(),
'<tfoot a="1" b="2"><tr><td>&lt;&gt;</td></tr></tfoot>')
def test_COL(self):
# Empty COL test
self.assertEqual(COL().xml(), '<col />')
# Not Empty COL test
self.assertEqual(COL(_span='2').xml(), '<col span="2" />')
# Commented for now not so sure how to make it pass properly was passing locally
# I think this test is interesting and add value
# This fail relate to python 2.6 limitation I think
# Failing COL test
# with self.assertRaises(SyntaxError) as cm:
# COL('<>').xml()
# self.assertEqual(cm.exception[0], '<col/> tags cannot have components')
# For now
self.assertRaises(SyntaxError, COL, '<>')
def test_COLGROUP(self):
# Empty COLGROUP test
self.assertEqual(COLGROUP().xml(), '<colgroup></colgroup>')
# Not Empty COLGROUP test
self.assertEqual(COLGROUP('<>', _a='1', _b='2').xml(), '<colgroup a="1" b="2">&lt;&gt;</colgroup>')
def test_TABLE(self):
self.assertEqual(TABLE('<>', _a='1', _b='2').xml(),
'<table a="1" b="2"><tr><td>&lt;&gt;</td></tr>' +
'</table>')
def test_I(self):
self.assertEqual(I('<>', _a='1', _b='2').xml(),
'<i a="1" b="2">&lt;&gt;</i>')
def test_IFRAME(self):
self.assertEqual(IFRAME('<>', _a='1', _b='2').xml(),
'<iframe a="1" b="2">&lt;&gt;</iframe>')
def test_INPUT(self):
self.assertEqual(INPUT(_a='1', _b='2').xml(), '<input a="1" b="2" type="text" />')
# list value
self.assertEqual(INPUT(_value=[1, 2, 3]).xml(), '<input type="text" value="[1, 2, 3]" />')
def test_TEXTAREA(self):
self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(),
'<textarea a="1" b="2" cols="40" rows="10">&lt;&gt;' +
'</textarea>')
# override _rows and _cols
self.assertEqual(TEXTAREA('<>', _a='1', _b='2', _rows=5, _cols=20).xml(),
'<textarea a="1" b="2" cols="20" rows="5">&lt;&gt;' +
'</textarea>')
self.assertEqual(TEXTAREA('<>', value='bla bla bla...', _rows=10, _cols=40).xml(),
'<textarea cols="40" rows="10">bla bla bla...</textarea>')
def test_OPTION(self):
self.assertEqual(OPTION('<>', _a='1', _b='2').xml(),
'<option a="1" b="2" value="&lt;&gt;">&lt;&gt;' +
'</option>')
def test_OBJECT(self):
self.assertEqual(OBJECT('<>', _a='1', _b='2').xml(),
'<object a="1" b="2">&lt;&gt;</object>')
def test_OPTGROUP(self):
# Empty OPTGROUP test
self.assertEqual(OPTGROUP().xml(),
'<optgroup></optgroup>')
# Not Empty OPTGROUP test
self.assertEqual(OPTGROUP('<>', _a='1', _b='2').xml(),
'<optgroup a="1" b="2"><option value="&lt;&gt;">&lt;&gt;</option></optgroup>')
# With an OPTION
self.assertEqual(OPTGROUP(OPTION('Option 1', _value='1'), _label='Group 1').xml(),
'<optgroup label="Group 1"><option value="1">Option 1</option></optgroup>')
def test_SELECT(self):
self.assertEqual(SELECT('<>', _a='1', _b='2').xml(),
'<select a="1" b="2">' +
'<option value="&lt;&gt;">&lt;&gt;</option></select>')
self.assertEqual(SELECT(OPTION('option 1', _value='1'),
OPTION('option 2', _value='2')).xml(),
'<select><option value="1">option 1</option><option value="2">option 2</option></select>')
self.assertEqual(SELECT(OPTION('option 1', _value='1', _selected='selected'),
OPTION('option 2', _value='2'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option selected="selected" value="1">option 1</option><option value="2">option 2</option></select>')
# More then one select with mutilple
self.assertEqual(SELECT(OPTION('option 1', _value='1', _selected='selected'),
OPTION('option 2', _value='2', _selected='selected'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option selected="selected" value="1">option 1</option><option selected="selected" value="2">option 2</option></select>'
)
# OPTGROUP
self.assertEqual(SELECT(OPTGROUP(OPTION('option 1', _value='1'),
OPTION('option 2', _value='2'),
_label='Group 1',)).xml(),
'<select><optgroup label="Group 1"><option value="1">option 1</option><option value="2">option 2</option></optgroup></select>')
# List
self.assertEqual(SELECT([1, 2, 3, 4, 5]).xml(),
'<select><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option></select>')
# Tuple
self.assertEqual(SELECT((1, 2, 3, 4, 5)).xml(),
'<select><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option></select>')
# String value
self.assertEqual(SELECT('Option 1', 'Option 2').xml(),
'<select><option value="Option 1">Option 1</option><option value="Option 2">Option 2</option></select>')
# list as a value
self.assertEqual(SELECT(OPTION('option 1', _value=[1, 2, 3]),
OPTION('option 2', _value=[4, 5, 6], _selected='selected'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option value="[1, 2, 3]">option 1</option><option selected="selected" value="[4, 5, 6]">option 2</option></select>')
def test_FIELDSET(self):
self.assertEqual(FIELDSET('<>', _a='1', _b='2').xml(),
'<fieldset a="1" b="2">&lt;&gt;</fieldset>')
def test_LEGEND(self):
self.assertEqual(LEGEND('<>', _a='1', _b='2').xml(),
'<legend a="1" b="2">&lt;&gt;</legend>')
def test_FORM(self):
self.assertEqual(FORM('<>', _a='1', _b='2').xml(),
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post">&lt;&gt;</form>')
# These 2 crash AppVeyor and Travis with: "ImportError: No YAML serializer available"
# self.assertEqual(FORM('<>', _a='1', _b='2').as_yaml(),
# "accepted: null\nattributes: {_a: '1', _action: '#', _b: '2', _enctype: multipart/form-data, _method: post}\ncomponents: [<>]\nerrors: {}\nlatest: {}\nparent: null\nvars: {}\n")
# self.assertEqual(FORM('<>', _a='1', _b='2').as_xml(),
# '<?xml version="1.0" encoding="UTF-8"?><document><errors></errors><vars></vars><parent>None</parent><attributes><_enctype>multipart/form-data</_enctype><_action>#</_action><_b>2</_b><_a>1</_a><_method>post</_method></attributes><components><item>&amp;lt;&amp;gt;</item></components><accepted>None</accepted><latest></latest></document>')
def test_BEAUTIFY(self):
self.assertEqual(BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml(),
'<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>')
# unicode
self.assertEqual(BEAUTIFY([P(u'àéèûôç'), 'a', 'b', {'hello': 'world'}]).xml(),
'<div><table><tr><td><div><p>\xc3\xa0\xc3\xa9\xc3\xa8\xc3\xbb\xc3\xb4\xc3\xa7</p></div></td></tr><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>')
def test_MENU(self):
self.assertEqual(MENU([('Home', False, '/welcome/default/index', [])]).xml(),
'<ul class="web2py-menu web2py-menu-vertical"><li class="web2py-menu-first"><a href="/welcome/default/index">Home</a></li></ul>')
# Multiples entries menu
self.assertEqual(MENU([('Home', False, '/welcome/default/index', []),
('Item 1', False, '/welcome/default/func_one', []),
('Item 2', False, '/welcome/default/func_two', []),
('Item 3', False, '/welcome/default/func_three', []),
('Item 4', False, '/welcome/default/func_four', [])]).xml(),
'<ul class="web2py-menu web2py-menu-vertical"><li class="web2py-menu-first"><a href="/welcome/default/index">Home</a></li><li><a href="/welcome/default/func_one">Item 1</a></li><li><a href="/welcome/default/func_two">Item 2</a></li><li><a href="/welcome/default/func_three">Item 3</a></li><li class="web2py-menu-last"><a href="/welcome/default/func_four">Item 4</a></li></ul>'
)
# mobile=True
self.assertEqual(MENU([('Home', False, '/welcome/default/index', [])], mobile=True).xml(),
'<select class="web2py-menu web2py-menu-vertical" onchange="window.location=this.value"><option value="/welcome/default/index">Home</option></select>')
# Multiples entries menu for mobile
self.assertEqual(MENU([('Home', False, '/welcome/default/index', []),
('Item 1', False, '/welcome/default/func_one', []),
('Item 2', False, '/welcome/default/func_two', []),
('Item 3', False, '/welcome/default/func_three', []),
('Item 4', False, '/welcome/default/func_four', [])], mobile=True).xml(),
'<select class="web2py-menu web2py-menu-vertical" onchange="window.location=this.value"><option value="/welcome/default/index">Home</option><option value="/welcome/default/func_one">Item 1</option><option value="/welcome/default/func_two">Item 2</option><option value="/welcome/default/func_three">Item 3</option><option value="/welcome/default/func_four">Item 4</option></select>')
# TODO: def test_embed64(self):
# TODO: def test_web2pyHTMLParser(self):
# TODO: def test_markdown_serializer(self):
# TODO: def test_markmin_serializer(self):
def test_MARKMIN(self):
# This test pass with python 2.7 but expected to fail under 2.6
# with self.assertRaises(TypeError) as cm:
# MARKMIN().xml()
# self.assertEqual(cm.exception[0], '__init__() takes at least 2 arguments (1 given)')
# For now
self.assertRaises(TypeError, MARKMIN)
self.assertEqual(MARKMIN('').xml(), '')
self.assertEqual(MARKMIN('<>').xml(),
'<p>&lt;&gt;</p>')
self.assertEqual(MARKMIN("``hello_world = 'Hello World!'``:python").xml(),
'<code class="python">hello_world = \'Hello World!\'</code>')
self.assertEqual(MARKMIN('<>').flatten(), '<>')
def test_ASSIGNJS(self):
# empty assignation
self.assertEqual(ASSIGNJS().xml(), '')
# text assignation
self.assertEqual(ASSIGNJS(var1='1', var2='2').xml(), 'var var1 = "1";\nvar var2 = "2";\n')
# int assignation
self.assertEqual(ASSIGNJS(var1=1, var2=2).xml(), 'var var1 = 1;\nvar var2 = 2;\n')
class TestData(unittest.TestCase):
def testAdata(self):
def test_Adata(self):
self.assertEqual(A('<>', data=dict(abc='<def?asd>', cde='standard'), _a='1', _b='2').xml(),
'<a a="1" b="2" data-abc="&lt;def?asd&gt;" data-cde="standard">&lt;&gt;</a>')
'<a a="1" b="2" data-abc="&lt;def?asd&gt;" data-cde="standard">&lt;&gt;</a>')
if __name__ == '__main__':
+36 -2
View File
@@ -23,8 +23,8 @@ class TestRecfile(unittest.TestCase):
def tearDown(self):
shutil.rmtree('tests')
def testgeneration(self):
for k in range(20):
def test_generation(self):
for k in range(10):
teststring = 'test%s' % k
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
with recfile.open(filename, "w") as g:
@@ -35,6 +35,40 @@ class TestRecfile(unittest.TestCase):
recfile.remove(filename)
is_there = recfile.exists(filename)
self.assertFalse(is_there)
for k in range(10):
teststring = 'test%s' % k
filename = str(uuid.uuid4()) + '.test'
with recfile.open(filename, "w", path='tests') as g:
g.write(teststring)
self.assertEqual(recfile.open(filename, "r", path='tests').read(), teststring)
is_there = recfile.exists(filename, path='tests')
self.assertTrue(is_there)
recfile.remove(filename, path='tests')
is_there = recfile.exists(filename, path='tests')
self.assertFalse(is_there)
for k in range(10):
teststring = 'test%s' % k
filename = os.path.join('tests', str(uuid.uuid4()), str(uuid.uuid4()) + '.test')
with recfile.open(filename, "w") as g:
g.write(teststring)
self.assertEqual(recfile.open(filename, "r").read(), teststring)
is_there = recfile.exists(filename)
self.assertTrue(is_there)
recfile.remove(filename)
is_there = recfile.exists(filename)
self.assertFalse(is_there)
def test_existing(self):
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
with open(filename, 'w') as g:
g.write('this file exists')
self.assertTrue(recfile.exists(filename))
self.assertTrue(hasattr(recfile.open(filename, "r"), 'read'))
recfile.remove(filename, path='tests')
self.assertFalse(recfile.exists(filename))
self.assertRaises(IOError, recfile.remove, filename)
self.assertRaises(IOError, recfile.open, filename, "r")
if __name__ == '__main__':
unittest.main()
+3
View File
@@ -0,0 +1,3 @@
# TODO : I think we should continue to use pathoc (http://pathod.net/docs/pathoc) for tests but integrate the call in
# gluon/tests so they run automatically. No need to make our own tests.
# ref: https://groups.google.com/d/msg/web2py-developers/Cjye8_hXZk8/AXbftS3sCgAJ
+526
View File
@@ -0,0 +1,526 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.scheduler
"""
import os
import unittest
import glob
import datetime
import sys
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.storage import Storage
from gluon.languages import translator
from gluon.scheduler import JobGraph, Scheduler
from gluon.dal import DAL
class BaseTestScheduler(unittest.TestCase):
def setUp(self):
self.db = None
self.cleanfolder()
from gluon import current
s = Storage({'application': 'welcome',
'folder': 'applications/welcome',
'controller': 'default'})
current.request = s
T = translator('', 'en')
current.T = T
self.db = DAL('sqlite://dummy2.db', check_reserved=['all'])
def cleanfolder(self):
if self.db:
self.db.close()
try:
os.unlink('dummy2.db')
except:
pass
tfiles = glob.glob('*_scheduler*.table')
for a in tfiles:
os.unlink(a)
def tearDown(self):
self.cleanfolder()
try:
self.inner_teardown()
except:
pass
class TestsForJobGraph(BaseTestScheduler):
def testJobGraph(self):
s = Scheduler(self.db)
myjob = JobGraph(self.db, 'job_1')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob.add_deps(shoes.id, socks.id)
## results in the following topological sort
# 9,3,6 --> 4,5 --> 8,7 --> 2
# socks, shirt, undershorts
# tie, pants
# shoes, belt
# jacket
known_toposort = [
set([socks.id, shirt.id, undershorts.id]),
set([tie.id, pants.id]),
set([shoes.id, belt.id]),
set([jacket.id])
]
toposort = myjob.validate('job_1')
self.assertEqual(toposort, known_toposort)
# add a cyclic dependency, jacket to undershorts
myjob.add_deps(undershorts.id, jacket.id)
# no exceptions raised, but result None
self.assertEqual(myjob.validate('job_1'), None)
def testJobGraphFailing(self):
s = Scheduler(self.db)
myjob = JobGraph(self.db, 'job_1')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob.add_deps(shoes.id, socks.id)
# add a cyclic dependency, jacket to undershorts
myjob.add_deps(undershorts.id, jacket.id)
# no exceptions raised, but result None
self.assertEqual(myjob.validate('job_1'), None)
# and no deps added
deps_inserted = self.db(self.db.scheduler_task_deps.id>0).count()
self.assertEqual(deps_inserted, 0)
def testJobGraphDifferentJobs(self):
s = Scheduler(self.db)
myjob1 = JobGraph(self.db, 'job_1')
myjob2 = JobGraph(self.db, 'job_2')
fname = 'foo'
# We have a few items to wear, and there's an "order" to respect...
# Items are: watch, jacket, shirt, tie, pants, undershorts, belt, shoes, socks
# Now, we can't put on the tie without wearing the shirt first, etc...
watch = s.queue_task(fname, task_name='watch')
jacket = s.queue_task(fname, task_name='jacket')
shirt = s.queue_task(fname, task_name='shirt')
tie = s.queue_task(fname, task_name='tie')
pants = s.queue_task(fname, task_name='pants')
undershorts = s.queue_task(fname, task_name='undershorts')
belt = s.queue_task(fname, task_name='belt')
shoes = s.queue_task(fname, task_name='shoes')
socks = s.queue_task(fname, task_name='socks')
# before the tie, comes the shirt
myjob1.add_deps(tie.id, shirt.id)
# before the belt too comes the shirt
myjob1.add_deps(belt.id, shirt.id)
# before the jacket, comes the tie
myjob1.add_deps(jacket.id, tie.id)
# before the belt, come the pants
myjob1.add_deps(belt.id, pants.id)
# before the shoes, comes the pants
myjob2.add_deps(shoes.id, pants.id)
# before the pants, comes the undershorts
myjob2.add_deps(pants.id, undershorts.id)
# before the shoes, comes the undershorts
myjob2.add_deps(shoes.id, undershorts.id)
# before the jacket, comes the belt
myjob2.add_deps(jacket.id, belt.id)
# before the shoes, comes the socks
myjob2.add_deps(shoes.id, socks.id)
# every job by itself can be completed
self.assertNotEqual(myjob1.validate('job_1'), None)
self.assertNotEqual(myjob1.validate('job_2'), None)
# and, implicitly, every queued task can be too
self.assertNotEqual(myjob1.validate(), None)
# add a cyclic dependency, jacket to undershorts
myjob2.add_deps(undershorts.id, jacket.id)
# every job can still be completed by itself
self.assertNotEqual(myjob1.validate('job_1'), None)
self.assertNotEqual(myjob1.validate('job_2'), None)
# but trying to see if every task will ever be completed fails
self.assertEqual(myjob2.validate(), None)
class TestsForSchedulerAPIs(BaseTestScheduler):
def testQueue_Task(self):
def isnotqueued(result):
self.assertEqual(result.id, None)
self.assertEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()) > 0, True)
def isqueued(result):
self.assertNotEqual(result.id, None)
self.assertNotEqual(result.uuid, None)
self.assertEqual(len(result.errors.keys()), 0)
s = Scheduler(self.db)
fname = 'foo'
watch = s.queue_task(fname, task_name='watch')
# queuing a task returns id, errors, uuid
self.assertEqual(set(watch.keys()), set(['id', 'uuid', 'errors']))
# queueing nothing isn't allowed
self.assertRaises(TypeError, s.queue_task, *[])
# passing pargs and pvars wrongly
# # pargs as dict
isnotqueued(s.queue_task(fname, dict(a=1), dict(b=1)))
# # pvars as list
isnotqueued(s.queue_task(fname, ['foo', 'bar'], ['foo', 'bar']))
# two tasks with the same uuid won't be there
isqueued(s.queue_task(fname, uuid='a'))
isnotqueued(s.queue_task(fname, uuid='a'))
# # #FIXME add here every parameter
def testTask_Status(self):
s = Scheduler(self.db)
fname = 'foo'
watch = s.queue_task(fname, task_name='watch')
# fetch status by id
by_id = s.task_status(watch.id)
# fetch status by uuid
by_uuid = s.task_status(watch.uuid)
# fetch status by query
by_query = s.task_status(self.db.scheduler_task.function_name == 'foo')
self.assertEqual(by_id, by_uuid)
self.assertEqual(by_id, by_query)
# fetch status by anything else throws
self.assertRaises(SyntaxError, s.task_status, *[[1, 2]])
# adding output returns the joined set, plus "result"
rtn = s.task_status(watch.id, output=True)
self.assertEqual(set(rtn.keys()), set(['scheduler_run', 'scheduler_task', 'result']))
class testForSchedulerRunnerBase(BaseTestScheduler):
def inner_teardown(self):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
os.unlink(fdest)
additional_files = [
os.path.join(current.request.folder, 'private', 'demo8.pholder')
]
for f in additional_files:
try:
os.unlink(f)
except:
pass
def writefunction(self, content, initlines=None):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
if initlines is None:
initlines = """
import os
import time
from gluon.scheduler import Scheduler
db_dal = os.path.abspath(os.path.join(request.folder, '..', '..', 'dummy2.db'))
sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal))
sched = Scheduler(sched_dal, max_empty_runs=15, migrate=False, heartbeat=1)
"""
with open(fdest, 'w') as q:
q.write(initlines)
q.write(content)
def exec_sched(self):
import subprocess
call_args = [sys.executable, 'web2py.py', '--no-banner', '-D', '20','-K', 'welcome']
ret = subprocess.call(call_args, env=dict(os.environ))
return ret
def fetch_results(self, sched, task):
info = sched.task_status(task.id)
task_runs = self.db(self.db.scheduler_run.task_id == task.id).select()
return info, task_runs
def exec_asserts(self, stmts, tag):
for stmt in stmts:
self.assertEqual(stmt[1], True, msg="%s - %s" % (tag, stmt[0]))
class TestsForSchedulerRunner(testForSchedulerRunnerBase):
def testRepeats_and_Expired_and_Prio(self):
s = Scheduler(self.db)
repeats = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), repeats=2, period=5)
a_while_ago = datetime.datetime.now() - datetime.timedelta(seconds=60)
expired = s.queue_task('demo4', stop_time=a_while_ago)
prio1 = s.queue_task('demo1', ['scheduled_first'])
prio2 = s.queue_task('demo1', ['scheduled_second'], next_run_time=a_while_ago)
self.db.commit()
self.writefunction(r"""
def demo1(*args,**vars):
print 'you passed args=%s and vars=%s' % (args, vars)
return args[0]
def demo4():
time.sleep(15)
print "I'm printing something"
return dict(a=1, b=2)
""")
ret = self.exec_sched()
self.assertEqual(ret, 0)
# repeats check
task, task_run = self.fetch_results(s, repeats)
res = [
("task status completed", task.status == 'COMPLETED'),
("task times_run is 2", task.times_run == 2),
("task ran 2 times only", len(task_run) == 2),
("scheduler_run records are COMPLETED ", (task_run[0].status == task_run[1].status == 'COMPLETED')),
("period is respected", (task_run[1].start_time > task_run[0].start_time + datetime.timedelta(seconds=task.period)))
]
self.exec_asserts(res, 'REPEATS')
# expired check
task, task_run = self.fetch_results(s, expired)
res = [
("task status expired", task.status == 'EXPIRED'),
("task times_run is 0", task.times_run == 0),
("task didn't run at all", len(task_run) == 0)
]
self.exec_asserts(res, 'EXPIRATION')
# prio check
task1 = s.task_status(prio1.id, output=True)
task2 = s.task_status(prio2.id, output=True)
res = [
("tasks status completed", task1.scheduler_task.status == task2.scheduler_task.status == 'COMPLETED'),
("priority2 was executed before priority1" , task1.scheduler_run.id > task2.scheduler_run.id)
]
self.exec_asserts(res, 'PRIORITY')
def testNoReturn_and_Timeout_and_Progress(self):
s = Scheduler(self.db)
noret1 = s.queue_task('demo5')
noret2 = s.queue_task('demo3')
timeout1 = s.queue_task('demo4', timeout=5)
timeout2 = s.queue_task('demo4')
progress = s.queue_task('demo6', sync_output=2)
self.db.commit()
self.writefunction(r"""
def demo3():
time.sleep(15)
print 1/0
return None
def demo4():
time.sleep(15)
print "I'm printing something"
return dict(a=1, b=2)
def demo5():
time.sleep(15)
print "I'm printing something"
rtn = dict(a=1, b=2)
def demo6():
time.sleep(5)
print '50%'
time.sleep(5)
print '!clear!100%'
return 1
""")
ret = self.exec_sched()
self.assertEqual(ret, 0)
# noreturn check
task1, task_run1 = self.fetch_results(s, noret1)
task2, task_run2 = self.fetch_results(s, noret2)
res = [
("tasks no_returns1 completed", task1.status == 'COMPLETED'),
("tasks no_returns2 failed", task2.status == 'FAILED'),
("no_returns1 doesn't have a scheduler_run record", len(task_run1) == 0),
("no_returns2 has a scheduler_run record FAILED", (len(task_run2) == 1 and task_run2[0].status == 'FAILED')),
]
self.exec_asserts(res, 'NO_RETURN')
# timeout check
task1 = s.task_status(timeout1.id, output=True)
task2 = s.task_status(timeout2.id, output=True)
res = [
("tasks timeouts1 timeoutted", task1.scheduler_task.status == 'TIMEOUT'),
("tasks timeouts2 completed", task2.scheduler_task.status == 'COMPLETED')
]
self.exec_asserts(res, 'TIMEOUT')
# progress check
task1 = s.task_status(progress.id, output=True)
res = [
("tasks percentages completed", task1.scheduler_task.status == 'COMPLETED'),
("output contains only 100%", task1.scheduler_run.run_output.strip() == "100%")
]
self.exec_asserts(res, 'PROGRESS')
def testDrift_and_env_and_immediate(self):
s = Scheduler(self.db)
immediate = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), immediate=True)
env = s.queue_task('demo7')
drift = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), period=93, prevent_drift=True)
self.db.commit()
self.writefunction(r"""
def demo1(*args,**vars):
print 'you passed args=%s and vars=%s' % (args, vars)
return args[0]
import random
def demo7():
time.sleep(random.randint(1,5))
print W2P_TASK, request.now
return W2P_TASK.id, W2P_TASK.uuid, W2P_TASK.run_id
""")
ret = self.exec_sched()
self.assertEqual(ret, 0)
# immediate check, can only check that nothing breaks
task1 = s.task_status(immediate.id)
res = [
("tasks status completed", task1.status == 'COMPLETED'),
]
self.exec_asserts(res, 'IMMEDIATE')
# drift check
task, task_run = self.fetch_results(s, drift)
res = [
("task status completed", task.status == 'COMPLETED'),
("next_run_time is exactly start_time + period", (task.next_run_time == task.start_time + datetime.timedelta(seconds=task.period)))
]
self.exec_asserts(res, 'DRIFT')
# env check
task1 = s.task_status(env.id, output=True)
res = [
("task %s returned W2P_TASK correctly" % (task1.scheduler_task.id), task1.result == [task1.scheduler_task.id, task1.scheduler_task.uuid, task1.scheduler_run.id]),
]
self.exec_asserts(res, 'ENV')
def testRetryFailed(self):
s = Scheduler(self.db)
failed = s.queue_task('demo2', retry_failed=1, period=1)
failed_consecutive = s.queue_task('demo8', retry_failed=2, repeats=2, period=1)
self.db.commit()
self.writefunction(r"""
def demo2():
1/0
def demo8():
placeholder = os.path.join(request.folder, 'private', 'demo8.pholder')
with open(placeholder, 'a') as g:
g.write('\nplaceholder for demo8 created')
num_of_lines = 0
with open(placeholder) as f:
num_of_lines = len([a for a in f.read().split('\n') if a])
print 'number of lines', num_of_lines
if num_of_lines <= 2:
1/0
else:
os.unlink(placeholder)
return 1
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
# failed - checks
task, task_run = self.fetch_results(s, failed)
res = [
("task status failed", task.status == 'FAILED'),
("task times_run is 0", task.times_run == 0),
("task times_failed is 2", task.times_failed == 2),
("task ran 2 times only", len(task_run) == 2),
("scheduler_run records are FAILED", (task_run[0].status == task_run[1].status == 'FAILED')),
("period is respected", (task_run[1].start_time > task_run[0].start_time + datetime.timedelta(seconds=task.period)))
]
self.exec_asserts(res, 'FAILED')
# failed consecutive - checks
task, task_run = self.fetch_results(s, failed_consecutive)
res = [
("task status completed", task.status == 'COMPLETED'),
("task times_run is 2", task.times_run == 2),
("task times_failed is 0", task.times_failed == 0),
("task ran 6 times", len(task_run) == 6),
("scheduler_run records for COMPLETED is 2", len([run.status for run in task_run if run.status == 'COMPLETED']) == 2),
("scheduler_run records for FAILED is 4", len([run.status for run in task_run if run.status == 'FAILED']) == 4),
]
self.exec_asserts(res, 'FAILED_CONSECUTIVE')
def testHugeResult(self):
s = Scheduler(self.db)
huge_result = s.queue_task('demo10', retry_failed=1, period=1)
self.db.commit()
self.writefunction(r"""
def demo10():
res = 'a' * 99999
return dict(res=res)
""")
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
# huge_result - checks
task = s.task_status(huge_result.id, output=True)
res = [
("task status completed", task.scheduler_task.status == 'COMPLETED'),
("task times_run is 1", task.scheduler_task.times_run == 1),
("result is the correct one", task.result == dict(res='a' * 99999))
]
self.exec_asserts(res, 'HUGE_RESULT')
if __name__ == '__main__':
unittest.main()
File diff suppressed because one or more lines are too long
+76 -1
View File
@@ -9,10 +9,11 @@ from fix_path import fix_sys_path
fix_sys_path(__file__)
import template
from template import render
class TestVirtualFields(unittest.TestCase):
class TestTemplate(unittest.TestCase):
def testRun(self):
self.assertEqual(render(content='{{for i in range(n):}}{{=i}}{{pass}}',
@@ -61,6 +62,80 @@ class TestVirtualFields(unittest.TestCase):
self.assertRaises(
SyntaxError, render, content='{{pass\n=list((1,2,\n3))}}')
def testWithDummyFileSystem(self):
from os.path import join as pjoin
import contextlib
from StringIO import StringIO
from gluon.restricted import RestrictedError
@contextlib.contextmanager
def monkey_patch(module, fn_name, patch):
try:
unpatch = getattr(module, fn_name)
except AttributeError:
unpatch = None
setattr(module, fn_name, patch)
try:
yield
finally:
if unpatch is None:
delattr(module, fn_name)
else:
setattr(module, fn_name, unpatch)
def dummy_open(path, mode):
if path == pjoin('views', 'layout.html'):
return StringIO("{{block left_sidebar}}left{{end}}"
"{{include}}"
"{{block right_sidebar}}right{{end}}")
elif path == pjoin('views', 'layoutbrackets.html'):
return StringIO("[[block left_sidebar]]left[[end]]"
"[[include]]"
"[[block right_sidebar]]right[[end]]")
elif path == pjoin('views', 'default', 'index.html'):
return StringIO("{{extend 'layout.html'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'indexbrackets.html'):
return StringIO("[[extend 'layoutbrackets.html']]"
"[[block left_sidebar]][[super]] [[end]]"
"to"
"[[block right_sidebar]] [[super]][[end]]")
elif path == pjoin('views', 'default', 'missing.html'):
return StringIO("{{extend 'wut'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'noescape.html'):
return StringIO("""{{=NOESCAPE('<script></script>')}}""")
raise IOError
with monkey_patch(template, 'open', dummy_open):
self.assertEqual(
render(filename=pjoin('views', 'default', 'index.html'),
path='views'),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', delimiters=('[[', ']]')),
'left to right')
self.assertRaises(
RestrictedError,
render,
filename=pjoin('views', 'default', 'missing.html'),
path='views')
response = template.DummyResponse()
response.delimiters = ('[[', ']]')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', context={'response': response}),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'noescape.html'),
context={'NOESCAPE': template.NOESCAPE}),
'<script></script>')
if __name__ == '__main__':
unittest.main()
+992 -36
View File
File diff suppressed because it is too large Load Diff
+72 -12
View File
@@ -10,6 +10,8 @@ fix_sys_path(__file__)
from utils import md5_hash
from utils import compare
from utils import is_valid_ip_address
from utils import web2py_uuid
import hashlib
from hashlib import md5, sha1, sha224, sha256, sha384, sha512
@@ -19,11 +21,7 @@ from utils import simple_hash, get_digest, secure_dumps, secure_loads
class TestUtils(unittest.TestCase):
""" Tests the utils.py module """
def test_md5_hash(self):
""" Tests the md5_hash function """
data = md5_hash("web2py rocks")
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
# TODO: def test_AES_new(self):
def test_compare(self):
""" Tests the compare funciton """
@@ -36,35 +34,62 @@ class TestUtils(unittest.TestCase):
compare_result_false = compare(a, b)
self.assertFalse(compare_result_false)
def test_md5_hash(self):
""" Tests the md5_hash function """
data = md5_hash("web2py rocks")
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
def test_simple_hash(self):
""" Tests the simple_hash function """
# no key, no salt, md5
# no key, no salt, digest_alg=None
self.assertRaises(RuntimeError, simple_hash, 'web2py rocks!', key='', salt='', digest_alg=None)
# no key, no salt, digest_alg = md5
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg=md5)
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
# no key, no salt, 'md5'
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg='md5')
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
# no key, no salt, sha1
# no key, no salt, 'sha1'
data_sha1 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha1')
self.assertEqual(data_sha1, '00489a46753d8db260c71542611cdef80652c4b7')
# no key, no salt, sha224
# no key, no salt, 'sha224'
data_sha224 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha224')
self.assertEqual(data_sha224, '84d7054271842c2c17983baa2b1447e0289d101140a8c002d49d60da')
# no key, no salt, sha256
# no key, no salt, 'sha256'
data_sha256 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha256')
self.assertEqual(data_sha256, '0849f224d8deb267e4598702aaec1bd749e6caec90832469891012a4be24af08')
# no key, no salt, sha384
# no key, no salt, 'sha384'
data_sha384 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha384')
self.assertEqual(data_sha384,
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
# no key, no salt, sha512
# no key, no salt, 'sha512'
data_sha512 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha512')
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
'64fdef25a038d6cc3fda1b2fb45d66461eeed5c4669e506ec8bdfee71348db7e')
# NOTE : get_digest() is covered by simple_hash tests above except raise error...
def test_get_digest(self):
# Bad algorithm
# Option 1, think not working with python 2.6
# with self.assertRaises(ValueError) as cm:
# get_digest('123')
# self.assertEqual(cm.exception[0], 'Invalid digest algorithm: 123')
# Option 2
self.assertRaises(ValueError, get_digest, '123')
# TODO: def test_get_callable_argspec(self):
# TODO: def test_pad(self):
def test_secure_dumps_and_loads(self):
""" Tests secure_dumps and secure_loads"""
testobj = {'a': 1, 'b': 2}
@@ -96,5 +121,40 @@ class TestUtils(unittest.TestCase):
wrong4 = secure_loads('abc', 'a', 'b')
self.assertEqual(wrong4, None)
# TODO: def test_initialize_urandom(self):
# TODO: def test_fast_urandom16(self):
def test_web2py_uuid(self):
from uuid import UUID
self.assertTrue(UUID(web2py_uuid()))
def test_is_valid_ip_address(self):
# IPv4
# False
# self.assertEqual(is_valid_ip_address('127.0'), False) # Fail with AppVeyor?? should pass
self.assertEqual(is_valid_ip_address('unknown'), False)
self.assertEqual(is_valid_ip_address(''), False)
# True
self.assertEqual(is_valid_ip_address('127.0.0.1'), True)
self.assertEqual(is_valid_ip_address('localhost'), True)
self.assertEqual(is_valid_ip_address('::1'), True)
# IPv6
# True
# Compressed
self.assertEqual(is_valid_ip_address('::ffff:7f00:1'), True) # IPv6 127.0.0.1 compressed
self.assertEqual(is_valid_ip_address('2001:660::1'), True)
# Expanded
self.assertEqual(is_valid_ip_address('0:0:0:0:0:ffff:7f00:1'), True) # IPv6 127.0.0.1 expanded
self.assertEqual(is_valid_ip_address('2607:fa48:6d50:69f1:21f:3cff:fe9d:9be3'), True) # Any address
# False
# self.assertEqual(is_valid_ip_address('2607:fa48:6d50:69f1:21f:3cff:fe9d:'), False) # Any address with mistake
# The above pass locally but fail with AppVeyor
# TODO: def test_is_loopback_ip_address(self):
# TODO: def test_getipaddrinfo(self):
if __name__ == '__main__':
unittest.main()
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -22,20 +22,21 @@ from urllib2 import HTTPError
webserverprocess = None
def startwebserver():
global webserverprocess
path = path = os.path.dirname(os.path.abspath(__file__))
if not os.path.isfile(os.path.join(path,'web2py.py')):
if not os.path.isfile(os.path.join(path, 'web2py.py')):
i = 0
while i<10:
while i < 10:
i += 1
if os.path.exists(os.path.join(path,'web2py.py')):
if os.path.exists(os.path.join(path, 'web2py.py')):
break
path = os.path.abspath(os.path.join(path, '..'))
web2py_exec = os.path.join(path, 'web2py.py')
webserverprocess = subprocess.Popen([sys.executable, web2py_exec, '-a', 'testpass'])
print 'Sleeping before web2py starts...'
for a in range(1,11):
for a in range(1, 11):
time.sleep(1)
print a, '...'
try:
@@ -46,10 +47,11 @@ def startwebserver():
continue
print ''
def terminate_process(pid):
#Taken from http://stackoverflow.com/questions/1064335/in-python-2-5-how-do-i-kill-a-subprocess
# Taken from http://stackoverflow.com/questions/1064335/in-python-2-5-how-do-i-kill-a-subprocess
# all this **blah** is because we are stuck with Python 2.5 and \
#we cannot use Popen.terminate()
# we cannot use Popen.terminate()
if sys.platform.startswith('win'):
import ctypes
PROCESS_TERMINATE = 1
@@ -59,10 +61,11 @@ def terminate_process(pid):
else:
os.kill(pid, signal.SIGKILL)
def stopwebserver():
global webserverprocess
print 'Killing webserver'
if sys.version_info < (2,6):
if sys.version_info < (2, 6):
terminate_process(webserverprocess.pid)
else:
webserverprocess.terminate()
@@ -108,7 +111,6 @@ class TestWeb(LiveTest):
# check registration and login were successful
client.get('index')
# COMMENTED BECAUSE FAILS BUT WHY?
self.assertTrue('Welcome Homer' in client.text)
client = WebClient('http://127.0.0.1:8000/admin/default/')
@@ -153,7 +155,7 @@ class TestWeb(LiveTest):
try:
s.post('examples/soap_examples/call/soap', data=xml_request, method="POST")
except HTTPError, e:
assert(e.msg=='INTERNAL SERVER ERROR')
assert(e.msg == 'INTERNAL SERVER ERROR')
# check internal server error returned (issue 153)
assert(s.status == 500)
assert(s.text == xml_response)
+182 -185
View File
@@ -23,6 +23,7 @@ import glob
import os
import re
import time
import fnmatch
import traceback
import smtplib
import urllib
@@ -819,12 +820,12 @@ class Recaptcha(DIV):
Examples:
Use as::
form = FORM(Recaptcha(public_key='...',private_key='...'))
form = FORM(Recaptcha(public_key='...', private_key='...'))
or::
form = SQLFORM(...)
form.append(Recaptcha(public_key='...',private_key='...'))
form.append(Recaptcha(public_key='...', private_key='...'))
"""
@@ -983,17 +984,17 @@ class Recaptcha2(DIV):
Examples:
Use as::
form = FORM(Recaptcha2(public_key='...',private_key='...'))
form = FORM(Recaptcha2(public_key='...', private_key='...'))
or::
form = SQLFORM(...)
form.append(Recaptcha2(public_key='...',private_key='...'))
form.append(Recaptcha2(public_key='...', private_key='...'))
to protect the login page instead, use::
from gluon.tools import Recaptcha2
auth.settings.captcha = Recaptcha2(request, public_key='...',private_key='...')
auth.settings.captcha = Recaptcha2(request, public_key='...', private_key='...')
"""
@@ -1216,7 +1217,7 @@ class AuthJWT(object):
self.auth = auth
self.algorithm = algorithm
if self.algorithm not in ('HS256', 'HS384', 'HS512'):
raise NotImplementedError('Algoritm %s not allowed' % algorithm)
raise NotImplementedError('Algorithm %s not allowed' % algorithm)
self.verify_expiration = verify_expiration
self.leeway = leeway
self.expiration = expiration
@@ -1313,6 +1314,7 @@ class AuthJWT(object):
We (mis)use the heavy default auth mechanism to avoid any further computation,
while sticking to a somewhat-stable Auth API.
"""
# TODO: Check the following comment
## is the following safe or should we use
## calendar.timegm(datetime.datetime.utcnow().timetuple())
## result seem to be the same (seconds since epoch, in UTC)
@@ -1394,9 +1396,10 @@ class AuthJWT(object):
self.alter_payload(payload)
ret = {'token': self.generate_token(payload)}
elif ret is None:
raise HTTP(
401, u'Not Authorized - need to be logged in, to pass a token for refresh or username and password for login',
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm})
raise HTTP(401,
u'Not Authorized - need to be logged in, to pass a token '
u'for refresh or username and password for login',
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm})
response.headers['Content-Type'] = 'application/json'
return serializers.json(ret)
@@ -1449,9 +1452,9 @@ class Auth(object):
everybody_group_id=None,
manager_actions={},
auth_manager_role=None,
two_factor_authentication_group = None,
auth_two_factor_enabled = False,
auth_two_factor_tries_left = 3,
two_factor_authentication_group=None,
auth_two_factor_enabled=False,
auth_two_factor_tries_left=3,
login_captcha=None,
register_captcha=None,
pre_registration_div=None,
@@ -1468,7 +1471,7 @@ class Auth(object):
on_failed_authentication=lambda x: redirect(x),
formstyle=None,
label_separator=None,
logging_enabled = True,
logging_enabled=True,
allow_delete_accounts=False,
password_field='password',
table_user_name='auth_user',
@@ -1717,13 +1720,32 @@ class Auth(object):
def here(self):
return URL(args=current.request.args, vars=current.request.get_vars)
def select_host(self, host, host_names=None):
"""
checks that host is valid, i.e. in the list of glob host_names
if the host is missing, then is it selects the first entry from host_names
read more here: https://github.com/web2py/web2py/issues/1196
"""
if host:
if host_names:
for item in host_names:
if fnmatch.fnmatch(host, item):
break
else:
raise HTTP(403, "Invalid Hostname")
elif host_names:
host = host_names[0]
else:
host = 'localhost'
return host
def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user',
cas_provider=None, signature=True, secure=False,
csrf_prevention=True, propagate_extension=None,
url_index=None, jwt=None, host=None):
url_index=None, jwt=None, host_names=None):
## next two lines for backward compatibility
# next two lines for backward compatibility
if not db and environment and isinstance(environment, DAL):
db = environment
self.db = db
@@ -1760,12 +1782,12 @@ class Auth(object):
url_index = url_index or URL(controller, 'index')
url_login = URL(controller, function, args='login',
extension = propagate_extension)
extension=propagate_extension)
# ## what happens after registration?
settings = self.settings = Settings()
settings.update(Auth.default_settings)
host = host or request.env.http_host
host = self.select_host(request.env.http_host, host_names)
settings.update(
cas_domains=[host],
enable_tokens=False,
@@ -1815,9 +1837,9 @@ class Auth(object):
hmac_key=hmac_key,
formstyle=current.response.formstyle,
label_separator=current.response.form_label_separator,
two_factor_methods = [],
two_factor_onvalidation = [],
host = host,
two_factor_methods=[],
two_factor_onvalidation=[],
host=host,
)
settings.lock_keys = True
# ## these are messages that can be customized
@@ -1912,7 +1934,7 @@ class Auth(object):
'reset_password', 'request_reset_password',
'change_password', 'profile', 'groups',
'impersonate', 'not_authorized', 'confirm_registration',
'bulk_register','manage_tokens','jwt'):
'bulk_register', 'manage_tokens', 'jwt'):
if len(request.args) >= 2 and args[0] == 'impersonate':
return getattr(self, args[0])(request.args[1])
else:
@@ -1954,10 +1976,8 @@ class Auth(object):
else:
next = '?_next=' + urllib.quote(URL(args=request.args,
vars=request.get_vars))
href = lambda function: '%s/%s%s' % (action, function, next
if referrer_actions is DEFAULT
or function in referrer_actions
else '')
href = lambda function: \
'%s/%s%s' % (action, function, next if referrer_actions is DEFAULT or function in referrer_actions else '')
if isinstance(prefix, str):
prefix = T(prefix)
if prefix:
@@ -1970,9 +1990,7 @@ class Auth(object):
if self.user_id: # User is logged in
logout_next = self.settings.logout_next
items.append({'name': T('Log Out'),
'href': '%s/logout?_next=%s' % (action,
urllib.quote(
logout_next)),
'href': '%s/logout?_next=%s' % (action, urllib.quote(logout_next)),
'icon': 'icon-off'})
if 'profile' not in self.settings.actions_disabled:
items.append({'name': T('Profile'), 'href': href('profile'),
@@ -2005,8 +2023,8 @@ class Auth(object):
if (self.settings.use_username and not
'retrieve_username' in self.settings.actions_disabled):
items.append({'name': T('Forgot username?'),
'href': href('retrieve_username'),
'icon': 'icon-edit'})
'href': href('retrieve_username'),
'icon': 'icon-edit'})
def menu(): # For inclusion in MENU
self.bar = [(items[0]['name'], False, items[0]['href'], [])]
@@ -2126,11 +2144,11 @@ class Auth(object):
if self.user_id:
self.bar = SPAN(prefix, user_identifier, s1,
Anr(items[0]['name'],
_href=items[0]['href']), s3,
_href=items[0]['href']), s3,
_class='auth_navbar')
else:
self.bar = SPAN(s1, Anr(items[0]['name'],
_href=items[0]['href']), s3,
_href=items[0]['href']), s3,
_class='auth_navbar')
for item in items[1:]:
self.bar.insert(-1, s2)
@@ -2187,11 +2205,10 @@ class Auth(object):
if ('id' in fieldnames and
'modified_on' in fieldnames and
not current_record in fieldnames):
table._enable_record_versioning(
archive_db=archive_db,
archive_name=archive_names,
current_record=current_record,
current_record_label=current_record_label)
table._enable_record_versioning(archive_db=archive_db,
archive_name=archive_names,
current_record=current_record,
current_record_label=current_record_label)
def define_signature(self):
db = self.db
@@ -2455,13 +2472,11 @@ class Auth(object):
settings.table_token_name,
Field('user_id', reference_table_user, default=None,
label=self.messages.label_user_id),
Field('expires_on', 'datetime', default=datetime.datetime(2999,12,31)),
Field('token',writable=False,default=web2py_uuid,unique=True),
Field('expires_on', 'datetime', default=datetime.datetime(2999, 12, 31)),
Field('token', writable=False, default=web2py_uuid, unique=True),
*extra_fields,
**dict(
migrate=self.__get_migrate(
settings.table_token_name, migrate),
fake_migrate=fake_migrate))
**dict(migrate=self.__get_migrate(settings.table_token_name, migrate),
fake_migrate=fake_migrate))
if not db._lazy_tables:
settings.table_user = db[settings.table_user_name]
settings.table_group = db[settings.table_group_name]
@@ -2512,9 +2527,7 @@ class Auth(object):
# log messages should not be translated
if type(description).__name__ == 'lazyT':
description = description.m
self.table_event().insert(
description=str(description % vars),
origin=origin, user_id=user_id)
self.table_event().insert(description=str(description % vars), origin=origin, user_id=user_id)
def get_or_create_user(self, keys, update_fields=['email'],
login=True, get=True):
@@ -2556,22 +2569,21 @@ class Auth(object):
update_keys[key] = keys[key]
user.update_record(**update_keys)
elif checks:
if not 'first_name' in keys and 'first_name' in table_user.fields:
if 'first_name' not in keys and 'first_name' in table_user.fields:
guess = keys.get('email', 'anonymous').split('@')[0]
keys['first_name'] = keys.get('username', guess)
form = table_user._filter_fields(keys)
user_id = table_user.insert(**form)
vars = table_user._filter_fields(keys)
user_id = table_user.insert(**vars)
user = table_user[user_id]
if self.settings.create_user_groups:
group_id = self.add_group(
self.settings.create_user_groups % user)
group_id = self.add_group(self.settings.create_user_groups % user)
self.add_membership(group_id, user_id)
if self.settings.everybody_group_id:
self.add_membership(self.settings.everybody_group_id, user_id)
if login:
self.user = user
if self.settings.register_onaccept:
callback(self.settings.register_onaccept, form)
callback(self.settings.register_onaccept, Storage(vars=user))
return user
def basic(self, basic_auth_realm=False):
@@ -2679,8 +2691,7 @@ class Auth(object):
fields[settings.passfield] = \
settings.table_user[settings.passfield].validate(fields[settings.passfield])[0]
if not fields.get(settings.userfield):
raise ValueError('register_bare: ' +
'userfield not provided or invalid')
raise ValueError('register_bare: userfield not provided or invalid')
user = self.get_or_create_user(fields, login=False, get=False,
update_fields=self.settings.update_fields)
if not user:
@@ -2731,7 +2742,7 @@ class Auth(object):
redirect(session._cas_service)
def cas_onaccept(form, onaccept=onaccept):
if not onaccept is DEFAULT:
if onaccept is not DEFAULT:
onaccept(form)
return allow_access(interactivelogin=True)
return self.login(next, onvalidation, cas_onaccept, log)
@@ -2818,14 +2829,14 @@ class Auth(object):
response = current.response
session = current.session
### use session for federated login
# use session for federated login
snext = self.get_vars_next()
if snext:
session._auth_next = snext
elif session._auth_next:
snext = session._auth_next
### pass
# pass
if next is DEFAULT:
# important for security
@@ -2931,7 +2942,7 @@ class Auth(object):
)
captcha = settings.login_captcha or \
(settings.login_captcha != False and settings.captcha)
(settings.login_captcha is not False and settings.captcha)
if captcha:
addrow(form, captcha.label, captcha, captcha.comment,
settings.formstyle, 'captcha__row')
@@ -2959,8 +2970,7 @@ class Auth(object):
elif temp_user.registration_key in ('disabled', 'blocked'):
response.flash = self.messages.login_disabled
return form
elif (temp_user.registration_key is not None
and temp_user.registration_key.strip()):
elif (temp_user.registration_key is not None and temp_user.registration_key.strip()):
response.flash = \
self.messages.registration_verifying
return form
@@ -3071,7 +3081,7 @@ class Auth(object):
subject=self.messages.retrieve_two_factor_code_subject,
message=self.messages.retrieve_two_factor_code.format(session.auth_two_factor))
else:
#Check for all method. It is possible to have multiples
# Check for all method. It is possible to have multiples
for two_factor_method in two_factor_methods:
try:
# By default we use session.auth_two_factor generated before.
@@ -3087,8 +3097,6 @@ class Auth(object):
hideerror=settings.hideerror):
accepted_form = True
accepted_form = True
'''
The lists is executed after form validation for each of the corresponding action.
For example, in your model:
@@ -3232,12 +3240,16 @@ class Auth(object):
next = cas.logout_url(next)
current.session.auth = None
self.user = None
if self.settings.renew_session_onlogout:
current.session.renew(clear_session=not self.settings.keep_session_onlogout)
current.session.flash = self.messages.logged_out
if next is not None:
redirect(next)
def logout_bare(self):
self.logout(next=None, onlogout=None, log=None)
def register(self,
next=DEFAULT,
onvalidation=DEFAULT,
@@ -3289,7 +3301,7 @@ class Auth(object):
passfield = self.settings.password_field
formstyle = self.settings.formstyle
try: # Make sure we have our original minimum length as other auth forms change it
try: # Make sure we have our original minimum length as other auth forms change it
table_user[passfield].requires[-1].min_length = self.settings.password_min_length
except:
pass
@@ -3297,7 +3309,7 @@ class Auth(object):
if self.settings.register_verify_password:
if self.settings.register_fields is None:
self.settings.register_fields = [f.name for f in table_user if f.writable]
k = self.settings.register_fields.index("password")
k = self.settings.register_fields.index(passfield)
self.settings.register_fields.insert(k+1, "password_two")
extra_fields = [
Field("password_two", "password",
@@ -3330,7 +3342,7 @@ class Auth(object):
key = web2py_uuid()
if self.settings.registration_requires_approval:
key = 'pending-'+key
key = 'pending-' + key
table_user.registration_key.default = key
if form.accepts(request, session if self.csrf_prevention else None,
@@ -3339,12 +3351,10 @@ class Auth(object):
hideerror=self.settings.hideerror):
description = self.messages.group_description % form.vars
if self.settings.create_user_groups:
group_id = self.add_group(
self.settings.create_user_groups % form.vars, description)
group_id = self.add_group(self.settings.create_user_groups % form.vars, description)
self.add_membership(group_id, form.vars.id)
if self.settings.everybody_group_id:
self.add_membership(
self.settings.everybody_group_id, form.vars.id)
self.add_membership(self.settings.everybody_group_id, form.vars.id)
if self.settings.registration_requires_verification:
link = self.url(
self.settings.function, args=('verify_email', key), scheme=True)
@@ -3362,8 +3372,7 @@ class Auth(object):
not self.settings.registration_requires_verification:
table_user[form.vars.id] = dict(registration_key='pending')
session.flash = self.messages.registration_pending
elif (not self.settings.registration_requires_verification or
self.settings.login_after_registration):
elif (not self.settings.registration_requires_verification or self.settings.login_after_registration):
if not self.settings.registration_requires_verification:
table_user[form.vars.id] = dict(registration_key='')
session.flash = self.messages.registration_successful
@@ -3441,7 +3450,7 @@ class Auth(object):
response = current.response
session = current.session
captcha = self.settings.retrieve_username_captcha or \
(self.settings.retrieve_username_captcha != False and self.settings.captcha)
(self.settings.retrieve_username_captcha is not False and self.settings.captcha)
if not self.settings.mailer:
response.flash = self.messages.function_disabled
return ''
@@ -3599,7 +3608,7 @@ class Auth(object):
if self.settings.prevent_password_reset_attacks:
key = request.vars.key
if not key and len(request.args)>1:
if not key and len(request.args) > 1:
key = request.args[-1]
if key:
session._reset_password_key = key
@@ -3709,7 +3718,7 @@ class Auth(object):
def manage_tokens(self):
if not self.user:
redirect(self.settings.login_url)
table_token =self.table_token()
table_token = self.table_token()
table_token.user_id.writable = False
table_token.user_id.default = self.user.id
table_token.token.writable = False
@@ -3773,8 +3782,7 @@ class Auth(object):
requires=self.table_user()[passfield].requires),
Field('new_password2', 'password',
label=self.messages.verify_password,
requires=[IS_EXPR(
'value==%s' % repr(request.vars.new_password),
requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
self.messages.mismatched_password)]),
submit_button=self.messages.password_reset_button,
hidden=dict(_next=next),
@@ -3808,7 +3816,7 @@ class Auth(object):
response = current.response
session = current.session
captcha = self.settings.retrieve_password_captcha or \
(self.settings.retrieve_password_captcha != False and self.settings.captcha)
(self.settings.retrieve_password_captcha is not False and self.settings.captcha)
if next is DEFAULT:
next = self.get_vars_next() or self.settings.request_reset_password_next
@@ -3853,7 +3861,7 @@ class Auth(object):
formname='reset_password', dbio=False,
onvalidation=onvalidation,
hideerror=self.settings.hideerror):
user = table_user(**{userfield:form.vars.get(userfield)})
user = table_user(**{userfield: form.vars.get(userfield)})
key = user.registration_key
if not user:
session.flash = self.messages['invalid_%s' % userfield]
@@ -4105,6 +4113,7 @@ class Auth(object):
raise HTTP(401, "Not Authorized")
current_id = auth.user.id
requested_id = user_id
user = None
if user_id is DEFAULT:
user_id = current.request.post_vars.user_id
if user_id and user_id != self.user.id and user_id != '0':
@@ -4133,7 +4142,10 @@ class Auth(object):
return None
if requested_id is DEFAULT and not request.post_vars:
return SQLFORM.factory(Field('user_id', 'integer'))
return SQLFORM(table_user, user.id, readonly=True)
elif not user:
return None
else:
return SQLFORM(table_user, user.id, readonly=True)
def update_groups(self):
if not self.user:
@@ -4215,10 +4227,8 @@ class Auth(object):
else:
next = self.here()
current.session.flash = current.response.flash
return call_or_redirect(
self.settings.on_failed_authentication,
self.settings.login_url +
'?_next=' + urllib.quote(next))
return call_or_redirect(self.settings.on_failed_authentication,
self.settings.login_url + '?_next=' + urllib.quote(next))
if callable(condition):
flag = condition()
@@ -4299,11 +4309,8 @@ class Auth(object):
"""
Creates a group associated to a role
"""
group_id = self.table_group().insert(
role=role, description=description)
self.log_event(self.messages['add_group_log'],
dict(group_id=group_id, role=role))
group_id = self.table_group().insert(role=role, description=description)
self.log_event(self.messages['add_group_log'], dict(group_id=group_id, role=role))
return group_id
def del_group(self, group_id):
@@ -4313,7 +4320,8 @@ class Auth(object):
self.db(self.table_group().id == group_id).delete()
self.db(self.table_membership().group_id == group_id).delete()
self.db(self.table_permission().group_id == group_id).delete()
if group_id in self.user_groups: del self.user_groups[group_id]
if group_id in self.user_groups:
del self.user_groups[group_id]
self.log_event(self.messages.del_group_log, dict(group_id=group_id))
def id_group(self, role):
@@ -4345,7 +4353,6 @@ class Auth(object):
"""
Checks if user is member of group_id or role
"""
group_id = group_id or self.id_group(role)
try:
group_id = int(group_id)
@@ -4354,8 +4361,8 @@ class Auth(object):
if not user_id and self.user:
user_id = self.user.id
membership = self.table_membership()
if group_id and user_id and self.db((membership.user_id == user_id)
& (membership.group_id == group_id)).select():
if group_id and user_id and self.db((membership.user_id == user_id) &
(membership.group_id == group_id)).select():
r = True
else:
r = False
@@ -4378,8 +4385,8 @@ class Auth(object):
user_id = self.user.id
membership = self.table_membership()
db = membership._db
record = db((membership.user_id==user_id)&
(membership.group_id==group_id),
record = db((membership.user_id == user_id) &
(membership.group_id == group_id),
ignore_common_filters=True).select().first()
if record:
if hasattr(record, 'is_active') and not record.is_active:
@@ -4402,15 +4409,18 @@ class Auth(object):
"""
group_id = group_id or self.id_group(role)
try:
group_id = int(group_id)
except:
group_id = self.id_group(group_id) # interpret group_id as a role
if not user_id and self.user:
user_id = self.user.id
membership = self.table_membership()
self.log_event(self.messages['del_membership_log'],
dict(user_id=user_id, group_id=group_id))
ret = self.db(membership.user_id
== user_id)(membership.group_id
== group_id).delete()
if group_id in self.user_groups: del self.user_groups[group_id]
ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete()
if group_id in self.user_groups:
del self.user_groups[group_id]
return ret
def has_permission(self,
@@ -4427,34 +4437,32 @@ class Auth(object):
"""
if not group_id and self.settings.everybody_group_id and \
self.has_permission(
name, table_name, record_id, user_id=None,
group_id=self.settings.everybody_group_id):
self.has_permission(name, table_name, record_id, user_id=None,
group_id=self.settings.everybody_group_id):
return True
if not user_id and not group_id and self.user:
user_id = self.user.id
if user_id:
membership = self.table_membership()
rows = self.db(membership.user_id
== user_id).select(membership.group_id)
rows = self.db(membership.user_id == user_id).select(membership.group_id)
groups = set([row.group_id for row in rows])
if group_id and group_id not in groups:
return False
else:
groups = set([group_id])
permission = self.table_permission()
rows = self.db(permission.name == name)(permission.table_name
== str(table_name))(permission.record_id
== record_id).select(permission.group_id)
rows = self.db(permission.name ==
name)(permission.table_name ==
str(table_name))(permission.record_id ==
record_id).select(permission.group_id)
groups_required = set([row.group_id for row in rows])
if record_id:
rows = self.db(permission.name
== name)(permission.table_name
== str(table_name))(permission.record_id
== 0).select(permission.group_id)
groups_required = groups_required.union(set([row.group_id
for row in rows]))
rows = self.db(permission.name ==
name)(permission.table_name ==
str(table_name))(permission.record_id ==
0).select(permission.group_id)
groups_required = groups_required.union(set([row.group_id for row in rows]))
if groups.intersection(groups_required):
r = True
else:
@@ -4478,14 +4486,14 @@ class Auth(object):
permission = self.table_permission()
if group_id == 0:
group_id = self.user_group()
record = self.db((permission.group_id == group_id)&
(permission.name == name)&
(permission.table_name == str(table_name))&
record = self.db((permission.group_id == group_id) &
(permission.name == name) &
(permission.table_name == str(table_name)) &
(permission.record_id == long(record_id)),
ignore_common_filters=True).select(
limitby=(0, 1), orderby_on_limitby=False).first()
ignore_common_filters=True
).select(limitby=(0, 1), orderby_on_limitby=False).first()
if record:
if hasattr(record, 'is_active') and not record.is_ctive:
if hasattr(record, 'is_active') and not record.is_active:
record.update_record(is_active=True)
id = record.id
else:
@@ -4512,10 +4520,11 @@ class Auth(object):
self.log_event(self.messages['del_permission_log'],
dict(group_id=group_id, name=name,
table_name=table_name, record_id=record_id))
return self.db(permission.group_id == group_id)(permission.name
== name)(permission.table_name
== str(table_name))(permission.record_id
== long(record_id)).delete()
return self.db(permission.group_id ==
group_id)(permission.name ==
name)(permission.table_name ==
str(table_name))(permission.record_id ==
long(record_id)).delete()
def accessible_query(self, name, table, user_id=None):
"""
@@ -4542,10 +4551,9 @@ class Auth(object):
cquery = table
tablenames = db._adapter.tables(cquery)
for tablename in tablenames:
cquery &= self.accessible_query(name, tablename,
user_id=user_id)
cquery &= self.accessible_query(name, tablename, user_id=user_id)
return cquery
if not isinstance(table, str) and\
if not isinstance(table, str) and \
self.has_permission(name, table, 0, user_id):
return table.id > 0
membership = self.table_membership()
@@ -4574,11 +4582,11 @@ class Auth(object):
If you have a table (db.mytable) that needs full revision history you
can just do::
form=crud.update(db.mytable,myrecord,onaccept=auth.archive)
form = crud.update(db.mytable, myrecord, onaccept=auth.archive)
or::
form=SQLFORM(db.mytable,myrecord).process(onaccept=auth.archive)
form = SQLFORM(db.mytable, myrecord).process(onaccept=auth.archive)
crud.archive will define a new table "mytable_archive" and store
a copy of the current record (if archive_current=True)
@@ -4592,18 +4600,18 @@ class Auth(object):
in a model::
db.define_table('mytable_archive',
Field('current_record',db.mytable),
db.mytable)
Field('current_record', db.mytable),
db.mytable)
Notice such table includes all fields of db.mytable plus one: current_record.
crud.archive does not timestamp the stored record unless your original table
has a fields like::
db.define_table(...,
Field('saved_on','datetime',
default=request.now,update=request.now,writable=False),
Field('saved_by',auth.user,
default=auth.user_id,update=auth.user_id,writable=False),
Field('saved_on', 'datetime',
default=request.now, update=request.now, writable=False),
Field('saved_by', auth.user,
default=auth.user_id, update=auth.user_id, writable=False),
there is nothing special about these fields since they are filled before
the record is archived.
@@ -4612,15 +4620,14 @@ class Auth(object):
you can do, for example::
db.define_table('myhistory',
Field('parent_record',db.mytable),
db.mytable)
Field('parent_record', db.mytable), db.mytable)
and use it as::
form=crud.update(db.mytable,myrecord,
onaccept=lambda form:crud.archive(form,
archive_table=db.myhistory,
current_record='parent_record'))
form = crud.update(db.mytable, myrecord,
onaccept=lambda form:crud.archive(form,
archive_table=db.myhistory,
current_record='parent_record'))
"""
if not archive_current and not form.record:
@@ -4628,7 +4635,7 @@ class Auth(object):
table = form.table
if not archive_table:
archive_table_name = '%s_archive' % table
if not archive_table_name in table._db:
if archive_table_name not in table._db:
table._db.define_table(
archive_table_name,
Field(current_record, table),
@@ -4883,18 +4890,16 @@ class Crud(object):
self.deleted = False
captcha = self.settings.update_captcha or self.settings.captcha
if record and captcha:
addrow(form, captcha.label, captcha, captcha.comment,
self.settings.formstyle, 'captcha__row')
addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle, 'captcha__row')
captcha = self.settings.create_captcha or self.settings.captcha
if not record and captcha:
addrow(form, captcha.label, captcha, captcha.comment,
self.settings.formstyle, 'captcha__row')
addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle, 'captcha__row')
if request.extension not in ('html', 'load'):
(_session, _formname) = (None, None)
else:
(_session, _formname) = (
session, '%s/%s' % (table._tablename, form.record_id))
if not formname is DEFAULT:
if formname is not DEFAULT:
_formname = formname
keepvalues = self.settings.keepvalues
if request.vars.delete_this_record:
@@ -5183,7 +5188,7 @@ class Crud(object):
elif validate:
value, error = field.validate(txtval)
if not error:
### TODO deal with 'starts with', 'ends with', 'contains' on GAE
# TODO deal with 'starts with', 'ends with', 'contains' on GAE
query &= self.get_query(field, opval, value)
else:
row[3].append(DIV(error, _class='error'))
@@ -5196,7 +5201,7 @@ class Crud(object):
results = db(query).select(*selected, **attributes)
for r in refsearch:
results = results.find(r)
except: # hmmm, we should do better here
except: # TODO: hmmm, we should do better here
results = None
return form, results
@@ -5208,7 +5213,7 @@ def fetch(url, data=None, headers=None,
cookie=Cookie.SimpleCookie(),
user_agent='Mozilla/5.0'):
headers = headers or {}
if not data is None:
if data is not None:
data = urllib.urlencode(data)
if user_agent:
headers['User-agent'] = user_agent
@@ -5671,12 +5676,12 @@ class Service(object):
methods = self.jsonrpc_procedures
data = json_parser.loads(request.body.read())
jsonrpc_2 = data.get('jsonrpc')
if jsonrpc_2: #hand over to version 2 of the protocol
if jsonrpc_2: # hand over to version 2 of the protocol
return self.serve_jsonrpc2(data)
id, method, params = data.get('id'), data.get('method'), data.get('params', [])
if id is None:
return return_error(0, 100, 'missing id')
if not method in methods:
if method not in methods:
return return_error(id, 100, 'method "%s" does not exist' % method)
try:
if isinstance(params, dict):
@@ -5700,8 +5705,7 @@ class Service(object):
def return_response(id, result):
if not must_respond:
return None
return serializers.json({'jsonrpc': '2.0',
'id': id, 'result': result})
return serializers.json({'jsonrpc': '2.0', 'id': id, 'result': result})
def return_error(id, code, message=None, data=None):
error = {'code': code}
@@ -5712,9 +5716,7 @@ class Service(object):
error['message'] = message
if data is not None:
error['data'] = data
return serializers.json({'jsonrpc': '2.0',
'id': id,
'error': error})
return serializers.json({'jsonrpc': '2.0', 'id': id, 'error': error})
def validate(data):
"""
@@ -5774,7 +5776,7 @@ class Service(object):
return return_error(None, e.code, e.info)
id, method, params = data.get('id'), data['method'], data.get('params', '')
if not method in methods:
if method not in methods:
return return_error(id, -32601, data='Method "%s" does not exist' % method)
try:
if isinstance(params, dict):
@@ -5880,7 +5882,7 @@ class Service(object):
UL(LI("Location: %s" % dispatcher.location),
LI("Namespace: %s" % dispatcher.namespace),
LI("SoapAction: %s" % dispatcher.action),
),
),
H3("Sample SOAP XML Request Message:"),
CODE(sample_req_xml, language="xml"),
H3("Sample SOAP XML Response Message:"),
@@ -6014,7 +6016,7 @@ def prettydate(d, T=lambda x: x, utc=False):
return T('1 year' + suffix)
elif dt.days >= 60:
return T('%d months' + suffix) % int(dt.days / 30)
elif dt.days > 21:
elif dt.days >= 27: # 4 weeks ugly
return T('1 month' + suffix)
elif dt.days >= 14:
return T('%d weeks' + suffix) % int(dt.days / 7)
@@ -6420,10 +6422,9 @@ class Wiki(object):
args += value['args']
db.define_table(key, *args, **value['vars'])
if self.settings.templates is None and not \
self.settings.manage_permissions:
self.settings.templates = db.wiki_page.tags.contains('template') & \
db.wiki_page.can_read.contains('everybody')
if self.settings.templates is None and not self.settings.manage_permissions:
self.settings.templates = \
db.wiki_page.tags.contains('template') & db.wiki_page.can_read.contains('everybody')
def update_tags_insert(page, id, db=db):
for tag in page.tags or []:
@@ -6446,8 +6447,10 @@ class Wiki(object):
'wiki_editor' not in auth.user_groups.values() and
self.settings.groups == auth.user_groups.values()):
group = db.auth_group(role='wiki_editor')
gid = group.id if group else db.auth_group.insert(
role='wiki_editor')
if group:
gid = group.id
else:
db.auth_group.insert(role='wiki_editor')
auth.add_membership(gid)
settings.lock_keys = True
@@ -6474,9 +6477,8 @@ class Wiki(object):
groups = self.settings.groups
return ('wiki_editor' in groups or
(page is None and 'wiki_author' in groups) or
not page is None and (
set(groups).intersection(set(page.can_edit)) or
page.created_by == self.auth.user.id))
page is not None and (set(groups).intersection(set(page.can_edit)) or
page.created_by == self.auth.user.id))
def can_manage(self):
if not self.auth.user:
@@ -6497,7 +6499,7 @@ class Wiki(object):
return True
return False
### END POLICY
# END POLICY
def automenu(self):
"""adds the menu if not present"""
@@ -6715,20 +6717,15 @@ class Wiki(object):
if self.settings.templates:
fields.append(
Field("from_template", "reference wiki_page",
requires=IS_EMPTY_OR(
IS_IN_DB(db(self.settings.templates),
db.wiki_page._id,
'%(slug)s')),
comment=current.T(
"Choose Template or empty for new Page")))
requires=IS_EMPTY_OR(IS_IN_DB(db(self.settings.templates), db.wiki_page._id, '%(slug)s')),
comment=current.T("Choose Template or empty for new Page")))
form = SQLFORM.factory(*fields, **dict(_class="well"))
form.element("[type=submit]").attributes["_value"] = \
current.T("Create Page from Slug")
if form.process().accepted:
form.vars.from_template = 0 if not form.vars.from_template \
else form.vars.from_template
redirect(URL(args=('_edit', form.vars.slug, form.vars.from_template or 0))) # added param
form.vars.from_template = 0 if not form.vars.from_template else form.vars.from_template
redirect(URL(args=('_edit', form.vars.slug, form.vars.from_template or 0))) # added param
return dict(content=form)
def pages(self):
@@ -6819,25 +6816,25 @@ class Wiki(object):
mode = 0
if mode in (2, 3):
submenu.append((current.T('View Page'), None,
URL(controller, function, args=slug)))
URL(controller, function, args=slug)))
if mode in (1, 3):
submenu.append((current.T('Edit Page'), None,
URL(controller, function, args=('_edit', slug))))
URL(controller, function, args=('_edit', slug))))
if mode in (1, 2):
submenu.append((current.T('Edit Page Media'), None,
URL(controller, function, args=('_editmedia', slug))))
URL(controller, function, args=('_editmedia', slug))))
submenu.append((current.T('Create New Page'), None,
URL(controller, function, args=('_create'))))
# Moved next if to inside self.auth.user check
if self.can_manage():
submenu.append((current.T('Manage Pages'), None,
URL(controller, function, args=('_pages'))))
URL(controller, function, args=('_pages'))))
submenu.append((current.T('Edit Menu'), None,
URL(controller, function, args=('_edit', 'wiki-menu'))))
URL(controller, function, args=('_edit', 'wiki-menu'))))
# Also moved inside self.auth.user check
submenu.append((current.T('Search Pages'), None,
URL(controller, function, args=('_search'))))
URL(controller, function, args=('_search'))))
return menu
def search(self, tags=None, query=None, cloud=True, preview=True,
@@ -6855,7 +6852,7 @@ class Wiki(object):
if request.vars.q:
tags = [v.strip() for v in request.vars.q.split(',')]
tags = [v.lower() for v in tags if v]
if tags or not query is None:
if tags or query is not None:
db = self.auth.db
count = db.wiki_tag.wiki_page.count()
fields = [db.wiki_page.id, db.wiki_page.slug,
+5 -7
View File
@@ -83,11 +83,9 @@ def compare(a, b):
""" Compares two strings and not vulnerable to timing attacks """
if HAVE_COMPARE_DIGEST:
return hmac.compare_digest(a, b)
if len(a) != len(b):
return False
result = 0
for x, y in zip(a, b):
result |= ord(x) ^ ord(y)
result = len(a) ^ len(b)
for i in xrange(len(b)):
result |= ord(a[i%len(a)]) ^ ord(b[i])
return result == 0
@@ -172,7 +170,7 @@ def secure_dumps(data, encryption_key, hash_key=None, compression_level=None):
dump = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
if compression_level:
dump = zlib.compress(dump, compression_level)
key = pad(encryption_key[:32])
key = pad(encryption_key)[:32]
cipher, IV = AES_new(key)
encrypted_data = base64.urlsafe_b64encode(IV + cipher.encrypt(pad(dump)))
signature = hmac.new(hash_key, encrypted_data).hexdigest()
@@ -188,7 +186,7 @@ def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
actual_signature = hmac.new(hash_key, encrypted_data).hexdigest()
if not compare(signature, actual_signature):
return None
key = pad(encryption_key[:32])
key = pad(encryption_key)[:32]
encrypted_data = base64.urlsafe_b64decode(encrypted_data)
IV, encrypted_data = encrypted_data[:16], encrypted_data[16:]
cipher, _ = AES_new(key, IV=IV)
+40 -50
View File
@@ -141,7 +141,6 @@ class Validator(object):
def __call__(self, value):
raise NotImplementedError
return (value, None)
class IS_MATCH(Validator):
@@ -377,8 +376,8 @@ class IS_JSON(Validator):
def __call__(self, value):
try:
if self.native_json:
simplejson.loads(value) # raises error in case of malformed json
return (value, None) # the serialized value is not passed
simplejson.loads(value) # raises error in case of malformed json
return (value, None) # the serialized value is not passed
else:
return (simplejson.loads(value), None)
except JSONErrors:
@@ -460,7 +459,7 @@ class IS_IN_SET(Validator):
def __call__(self, value):
if self.multiple:
### if below was values = re.compile("[\w\-:]+").findall(str(value))
# if below was values = re.compile("[\w\-:]+").findall(str(value))
if not value:
values = []
elif isinstance(value, (tuple, list)):
@@ -472,8 +471,6 @@ class IS_IN_SET(Validator):
thestrset = [str(x) for x in self.theset]
failures = [x for x in values if not str(x) in thestrset]
if failures and self.theset:
if self.multiple and (value is None or value == ''):
return ([], None)
return (value, translate(self.error_message))
if self.multiple:
if isinstance(self.multiple, (tuple, list)) and \
@@ -526,8 +523,8 @@ class IS_IN_DB(Validator):
field = field._id
elif isinstance(field, str):
items = field.split('.')
if len(items)==1: items+=['id']
field = self.dbset.db[items[0]][items[1]]
if len(items) == 1:
field = items[0] + '.id'
(ktable, kfield) = str(field).split('.')
if not label:
@@ -537,16 +534,16 @@ class IS_IN_DB(Validator):
label = '%%(%s)s' % str(label).split('.')[-1]
fieldnames = regex2.findall(label)
if kfield not in fieldnames:
fieldnames.append(kfield) # kfield must be last
fieldnames.append(kfield) # kfield must be last
elif isinstance(label, Field):
fieldnames = [label.name, kfield] # kfield must be last
fieldnames = [label.name, kfield] # kfield must be last
label = '%%(%s)s' % label.name
elif callable(label):
fieldnames = '*'
else:
raise NotImplementedError
self.field = field # the lookup field
self.fieldnames = fieldnames # fields requires to build the formatting
self.fieldnames = fieldnames # fields requires to build the formatting
self.label = label
self.ktable = ktable
self.kfield = kfield
@@ -624,16 +621,16 @@ class IS_IN_DB(Validator):
if isinstance(value, list):
values = value
elif self.delimiter:
values = value.split(self.delimiter) # because of autocomplete
values = value.split(self.delimiter) # because of autocomplete
elif value:
values = [value]
else:
values = []
if self.field.type in ('id','integer'):
if field.type in ('id', 'integer'):
new_values = []
for value in values:
if not (isinstance(value,(int,long)) or value.isdigit()):
if not (isinstance(value, (int, long)) or value.isdigit()):
if self.auto_add:
value = str(self.maybe_add(table, self.fieldnames[0], value))
else:
@@ -648,11 +645,10 @@ class IS_IN_DB(Validator):
if not [v for v in values if v not in self.theset]:
return (values, None)
else:
from pydal.adapters import GoogleDatastoreAdapter
def count(values, s=self.dbset, f=field):
return s(f.belongs(map(int, values))).count()
if GoogleDatastoreAdapter is not None and isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
if self.dbset.db._adapter.dbengine == "google:datastore":
range_ids = range(0, len(values), 30)
total = sum(count(values[i:i + 30]) for i in range_ids)
if total == len(values):
@@ -660,8 +656,8 @@ class IS_IN_DB(Validator):
elif count(values) == len(values):
return (values, None)
else:
if self.field.type in ('id','integer'):
if isinstance(value,(int,long)) or value.isdigit():
if field.type in ('id', 'integer'):
if isinstance(value, (int, long)) or value.isdigit():
value = int(value)
elif self.auto_add:
value = self.maybe_add(table, self.fieldnames[0], value)
@@ -821,7 +817,7 @@ class IS_INT_IN_RANGE(Validator):
if regex_isint.match(str(value)):
v = int(value)
if ((self.minimum is None or v >= self.minimum) and
(self.maximum is None or v < self.maximum)):
(self.maximum is None or v < self.maximum)):
return (v, None)
return (value, self.error_message)
@@ -895,7 +891,7 @@ class IS_FLOAT_IN_RANGE(Validator):
else:
v = float(str(value).replace(self.dot, '.'))
if ((self.minimum is None or v >= self.minimum) and
(self.maximum is None or v <= self.maximum)):
(self.maximum is None or v <= self.maximum)):
return (v, None)
except (ValueError, TypeError):
pass
@@ -981,7 +977,7 @@ class IS_DECIMAL_IN_RANGE(Validator):
else:
v = decimal.Decimal(str(value).replace(self.dot, '.'))
if ((self.minimum is None or v >= self.minimum) and
(self.maximum is None or v <= self.maximum)):
(self.maximum is None or v <= self.maximum)):
return (v, None)
except (ValueError, TypeError, decimal.InvalidOperation):
pass
@@ -1201,7 +1197,12 @@ class IS_EMAIL(Validator):
self.error_message = error_message
def __call__(self, value):
match = self.regex.match(value)
try:
match = self.regex.match(value)
except TypeError:
# Value may not be a string where we can look for matches.
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
match = None
if match:
domain = value.split('@')[1]
if (not self.banned or not self.banned.match(domain)) \
@@ -2345,6 +2346,7 @@ class IS_DATE_IN_RANGE(IS_DATE):
(datetime.date(2010, 3, 3), 'oops')
"""
def __init__(self,
minimum=None,
maximum=None,
@@ -2398,6 +2400,7 @@ class IS_DATETIME_IN_RANGE(IS_DATETIME):
(datetime.datetime(2010, 3, 3, 0, 0), 'oops')
"""
def __init__(self,
minimum=None,
maximum=None,
@@ -2511,7 +2514,7 @@ def urlify(s, maxlen=80, keep_underscores=False):
if keep_underscores:
s = re.sub('\s+', '-', s) # whitespace to hyphens
s = re.sub('[^\w\-]', '', s)
# strip all but alphanumeric/underscore/hyphen
# strip all but alphanumeric/underscore/hyphen
else:
s = re.sub('[\s_]+', '-', s) # whitespace & underscores to hyphens
s = re.sub('[^a-z0-9\-]', '', s) # strip all but alphanumeric/hyphen
@@ -2609,7 +2612,7 @@ class ANY_OF(Validator):
# Use the formatter of the first subvalidator
# that validates the value and has a formatter
for validator in self.subs:
if hasattr(validator, 'formatter') and validator(value)[1] != None:
if hasattr(validator, 'formatter') and validator(value)[1] is None:
return validator.formatter(value)
@@ -2642,8 +2645,8 @@ class IS_EMPTY_OR(Validator):
if hasattr(other, 'options'):
self.options = self._options
def _options(self):
options = self.other.options()
def _options(self, *args, **kwargs):
options = self.other.options(*args, **kwargs)
if (not options or options[0][0] != '') and not self.multiple:
options.insert(0, ('', ''))
return options
@@ -2703,6 +2706,7 @@ class LazyCrypt(object):
"""
Stores a lazy password hash
"""
def __init__(self, crypt, password):
"""
crypt is an instance of the CRYPT validator,
@@ -2758,8 +2762,8 @@ class LazyCrypt(object):
# LazyCrypt objects comparison
if isinstance(stored_password, self.__class__):
return ((self is stored_password) or
((self.crypt.key == stored_password.crypt.key) and
(self.password == stored_password.password)))
((self.crypt.key == stored_password.crypt.key) and
(self.password == stored_password.password)))
if self.crypt.key:
if ':' in self.crypt.key:
@@ -3063,21 +3067,6 @@ class IS_STRONG(object):
return (value, translate(self.error_message))
class IS_IN_SUBSET(IS_IN_SET):
REGEX_W = re.compile('\w+')
def __init__(self, *a, **b):
IS_IN_SET.__init__(self, *a, **b)
def __call__(self, value):
values = self.REGEX_W.findall(str(value))
failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]]
if failures:
return (value, translate(self.error_message))
return (value, None)
class IS_IMAGE(Validator):
"""
Checks if file uploaded through file input was saved in one of selected
@@ -3417,14 +3406,14 @@ class IS_IPV4(Validator):
ok = True
if not (self.is_localhost is None or self.is_localhost ==
(number == self.localhost)):
ok = False
ok = False
if not (self.is_private is None or self.is_private ==
(sum([private_number[0] <= number <= private_number[1]
for private_number in self.private]) > 0)):
ok = False
ok = False
if not (self.is_automatic is None or self.is_automatic ==
(self.automatic[0] <= number <= self.automatic[1])):
ok = False
ok = False
if ok:
return (value, None)
return (value, translate(self.error_message))
@@ -3708,6 +3697,7 @@ class IS_IPADDRESS(Validator):
>>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
('2001::8ffa:fe22:b3af', 'invalid subnet provided')
"""
def __init__(
self,
minip='0.0.0.0',
@@ -3770,7 +3760,7 @@ class IS_IPADDRESS(Validator):
is_private=self.is_private,
is_automatic=self.is_automatic,
error_message=self.error_message
)(value)
)(value)
elif self.is_ipv6 or isinstance(ip, IPv6Address):
retval = IS_IPV6(
is_private=self.is_private,
@@ -3782,7 +3772,7 @@ class IS_IPADDRESS(Validator):
is_teredo=self.is_teredo,
subnets=self.subnets,
error_message=self.error_message
)(value)
)(value)
else:
retval = (value, translate(self.error_message))
+5 -9
View File
@@ -17,6 +17,7 @@ import time
import thread
import threading
import os
import copy
import socket
import signal
import math
@@ -940,7 +941,10 @@ def console():
sys.argv, other_args = sys.argv[:k], sys.argv[k + 1:]
(options, args) = parser.parse_args()
options.args = [options.run] + other_args
global_settings.cmd_options = options
copy_options = copy.deepcopy(options)
copy_options.password = '******'
global_settings.cmd_options = copy_options
global_settings.cmd_args = args
if options.gae:
@@ -1126,14 +1130,6 @@ def start(cron=True):
if hasattr(options, key):
setattr(options, key, getattr(options2, key))
logfile0 = os.path.join('examples', 'logging.example.conf')
logfile1 = os.path.join(options.folder, 'logging.conf')
if not os.path.exists(logfile1) and os.path.exists(logfile0):
import shutil
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
shutil.copyfile(logfile0, logfile1)
sys.stdout.write("OK\n")
# ## if -T run doctests (no cron)
if hasattr(options, 'test') and options.test:
test(options.test, verbose=options.verbose)
-16
View File
@@ -1,16 +0,0 @@
import time
import sys
import urllib2
import urllib2
n = int(sys.argv[1])
url = sys.argv[2]
headers = {"Accept-Language": "en"}
req = urllib2.Request(url, None, headers)
t0 = time.time()
for k in xrange(n):
data = urllib2.urlopen(req).read()
print (time.time() - t0) / n
if n == 1:
print data
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import re
filename = sys.argv[1]
datafile = open(filename, 'r')
try:
data = '\n' + datafile.read()
finally:
datafile.close()
SPACE = '\n ' if '-n' in sys.argv[1:] else ' '
data = re.compile('(?<!\:)//(?P<a>.*)').sub('/* \g<a> */', data)
data = re.compile('[ ]+').sub(' ', data)
data = re.compile('\s*{\s*').sub(' {' + SPACE, data)
data = re.compile('\s*;\s*').sub(';' + SPACE, data)
data = re.compile(',\s*').sub(', ', data)
data = re.compile('\s*\*/\s*').sub('*/' + SPACE, data)
data = re.compile('\s*}\s*').sub(SPACE + '}\n', data)
data = re.compile('\n\s*\n').sub('\n', data)
data = re.compile(';\s+/\*').sub('; /*', data)
data = re.compile('\*/\s+/\*').sub(' ', data)
data = re.compile('[ ]+\n').sub('\n', data)
data = re.compile('\n\s*/[\*]+(?P<a>.*?)[\*]+/', re.DOTALL).sub(
'\n/*\g<a>*/\n', data)
data = re.compile('[ \t]+(?P<a>\S.+?){').sub(' \g<a>{', data)
data = data.replace('}', '}\n')
print data
-67
View File
@@ -1,67 +0,0 @@
import sys
import re
def cleancss(text):
text = re.compile('\s+').sub(' ', text)
text = re.compile('\s*(?P<a>,|:)\s*').sub('\g<a> ', text)
text = re.compile('\s*;\s*').sub(';\n ', text)
text = re.compile('\s*\{\s*').sub(' {\n ', text)
text = re.compile('\s*\}\s*').sub('\n}\n\n', text)
return text
def cleanhtml(text):
text = text.lower()
r = re.compile('\<script.+?/script\>', re.DOTALL)
scripts = r.findall(text)
text = r.sub('<script />', text)
r = re.compile('\<style.+?/style\>', re.DOTALL)
styles = r.findall(text)
text = r.sub('<style />', text)
text = re.compile(
'<(?P<tag>(input|meta|link|hr|br|img|param))(?P<any>[^\>]*)\s*(?<!/)>')\
.sub('<\g<tag>\g<any> />', text)
text = text.replace('\n', ' ')
text = text.replace('>', '>\n')
text = text.replace('<', '\n<')
text = re.compile('\s*\n\s*').sub('\n', text)
lines = text.split('\n')
(indent, newlines) = (0, [])
for line in lines:
if line[:2] == '</': indent = indent - 1
newlines.append(indent * ' ' + line)
if not line[:2] == '</' and line[-1:] == '>' and \
not line[-2:] in ['/>', '->']: indent = indent + 1
text = '\n'.join(newlines)
text = re.compile(
'\<div(?P<a>( .+)?)\>\s+\</div\>').sub('<div\g<a>></div>', text)
text = re.compile('\<a(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</a\>').sub('<a\g<a>>\g<b></a>', text)
text = re.compile('\<b(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</b\>').sub('<b\g<a>>\g<b></b>', text)
text = re.compile('\<i(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</i\>').sub('<i\g<a>>\g<b></i>', text)
text = re.compile('\<span(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</span\>').sub('<span\g<a>>\g<b></span>', text)
text = re.compile('\s+\<br(?P<a>.*?)\/\>').sub('<br\g<a>/>', text)
text = re.compile('\>(?P<a>\s+)(?P<b>[\.\,\:\;])').sub('>\g<b>\g<a>', text)
text = re.compile('\n\s*\n').sub('\n', text)
for script in scripts:
text = text.replace('<script />', script, 1)
for style in styles:
text = text.replace('<style />', cleancss(style), 1)
return text
def read_file(filename):
f = open(filename, 'r')
try:
return f.read()
finally:
f.close()
for file in sys.argv[1:]:
data = read_file(file)
open(file+'.bak2', 'w').write(data)
if file[-4:] == '.css':
data = cleancss(data)
if file[-5:] == '.html':
data = cleanhtml(data)
open(file, 'w').write(data)

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