Compare commits

...

233 Commits

Author SHA1 Message Date
mdipierro 0a013e3edc R-2.15.2 2017-07-19 01:21:48 -05:00
mdipierro fb4c114d85 Merge pull request #1686 from leonelcamara/25issues
Fixes 6 issues
2017-07-18 03:44:19 -05:00
mdipierro b47e1334d5 Merge pull request #1682 from leonelcamara/fixheroku
Fixheroku
2017-07-18 03:43:45 -05:00
Leonel Câmara ce0b255747 Fixes #1672 2017-07-16 17:59:52 +01:00
Leonel Câmara 05d2ced779 remove print that was left from debugging 2017-07-16 14:27:11 +01:00
Leonel Câmara d144ff7d65 Fixes #1687 2017-07-16 14:06:03 +01:00
Leonel Câmara 780510dc32 Fixes #1684 2017-07-15 09:52:58 +01:00
Leonel Câmara 7a5f611e76 Fixes #1571 2017-07-14 20:23:13 +01:00
Leonel Câmara b7b8a009f2 Fixes #1680 2017-07-14 20:17:30 +01:00
Leonel Câmara 113df51ef9 Fixes #1685 2017-07-14 18:52:36 +01:00
mdipierro 4e704ca6f7 R-2.15.1 2017-07-10 16:18:35 -05:00
mdipierro 047ed786ac fixed some error in hashlib_md5 2017-07-10 16:02:15 -05:00
mdipierro ca1e5156ba fixed hashlib_md5 again, thanks Paolo 2017-07-10 15:10:16 -05:00
mdipierro 4854b84ff9 fixed a python 3 problem, thanks kato 2017-07-10 14:54:08 -05:00
mdipierro aa252cdbd8 Merge branch 'BuhtigithuB-improve/pep8-globals-py' 2017-07-10 14:20:45 -05:00
mdipierro e3ec4d4075 merged hashlib conflict 2017-07-10 14:20:39 -05:00
mdipierro 81b000d47a Merge branch 'master' of github.com:web2py/web2py 2017-07-10 14:19:08 -05:00
mdipierro 305dac4976 fixed changelog 2017-07-10 14:19:01 -05:00
mdipierro 876cc634a8 Merge pull request #1679 from leonelcamara/fix1675
Fixes #1675 dropdown not working in the "manage" button
2017-07-10 14:18:17 -05:00
mdipierro c004d2c16e Merge pull request #1678 from willimoa/issue/1674
Issue/1674
2017-07-10 14:17:56 -05:00
mdipierro e3cce4d752 Merge pull request #1669 from ilvalle/testing_pypy3_trusty
Testing pypy3.5 in travis, switching to ubuntu trusty
2017-07-10 14:17:20 -05:00
mdipierro 2c84b88466 Merge pull request #1668 from ilvalle/fix_compile_views
fix performance regression with compiled views
2017-07-10 14:17:04 -05:00
mdipierro fe652c851b Merge pull request #1667 from BuhtigithuB/improve/pep8-main-py
Enhance main.py PEP8
2017-07-10 14:16:09 -05:00
mdipierro c183c7b18b Merge pull request #1666 from abastardi/ajax-uploads
Enable Ajax file uploads
2017-07-10 14:15:01 -05:00
mdipierro e2c9875cd5 Merge pull request #1665 from BuhtigithuB/clean/python-2-6-remaining-code-in-import-all-py
Close #1664 remaining py2.6 specific code
2017-07-10 14:14:44 -05:00
mdipierro 579c54f926 Merge pull request #1663 from BuhtigithuB/improve/pep8-http-py
Enhance http.py PEP8
2017-07-10 14:14:09 -05:00
mdipierro b2cb0bc189 Merge pull request #1662 from BuhtigithuB/improve/pep8-html-py
Enhance html.py PEP8
2017-07-10 14:13:53 -05:00
mdipierro 3f6e8c755a Merge pull request #1660 from BuhtigithuB/improve/pep8-highlight-py
Enhance highlight.py PEP8
2017-07-10 14:13:19 -05:00
mdipierro 023d7f3e6c Merge pull request #1659 from BuhtigithuB/improve/pep8-decoder-py
Enhance decoder.py PEP8
2017-07-10 14:13:04 -05:00
mdipierro 01285ad4cd Merge pull request #1657 from BuhtigithuB/improve/pep8-dal-py
Enhance dal.py PEP8
2017-07-10 14:12:55 -05:00
mdipierro 0d855c1e9c Merge pull request #1656 from BuhtigithuB/improve/pep8-contenttype-py
Enhance contenttype.py PEP8
2017-07-10 14:12:45 -05:00
mdipierro 2d21c00e8d Merge pull request #1655 from BuhtigithuB/improve/pep8-compileapp-py
Enhance compileapp.py PEP8
2017-07-10 14:12:30 -05:00
mdipierro de9d0eb895 Merge pull request #1654 from BuhtigithuB/improve/pep8-admin-py
Enhance admin.py PEP8
2017-07-10 14:12:16 -05:00
mdipierro 9998916ef6 Merge pull request #1653 from BuhtigithuB/improve/pep8-validators-py
Enhance validators.py PEP8 and fix docstring py3 compatibility
2017-07-10 14:11:58 -05:00
mdipierro 453123a8ed Merge pull request #1652 from BuhtigithuB/improve/pep8-tools-py
Enhance tools.py PEP8 compliancy
2017-07-10 14:11:11 -05:00
mdipierro 2396cad2d1 Merge pull request #1658 from ilvalle/fix_minify
fix minify, added tests
2017-07-10 14:09:53 -05:00
mdipierro 540eecc207 R-2.15.0b2 2017-07-10 03:23:13 -05:00
mdipierro 83681f3f5d fixed markmin test, change beahvior 2017-07-10 03:16:42 -05:00
mdipierro b0a01ef720 fixed some relative imports, thanks steve.van.christen 2017-07-10 03:13:12 -05:00
mdipierro da5543f62e tracking newest pydal 2017-07-10 03:06:08 -05:00
mdipierro dc4ff7c3cc made markmin2html independens on gluon again and fixed reported problem, thanks mweissen 2017-07-10 03:01:35 -05:00
mdipierro 0223b0871e jquery 2.2.4 in admin 2017-07-10 02:43:56 -05:00
Leonel Câmara b3a7c20f3f Fixes #1635 2017-07-10 00:17:15 +01:00
Leonel Câmara 2fc4115718 update heroku.py to the new pydal 2017-07-09 15:35:48 +01:00
Leonel Câmara c6c027dbec Fixes #1675 dropdown not working in the "manage" button 2017-07-07 18:10:31 +01:00
Andrew Willimott 60edb11420 Explicit path added for graph css file. 2017-07-07 21:03:55 +12:00
Andrew Willimott 51ce3ffd36 Path to css file changed to explicitly point to admin app 2017-07-07 20:39:42 +12:00
mdipierro 89832479fc R-2.16.0b1 2017-07-05 01:48:28 -05:00
mdipierro 15ffdd9a20 R-2.16.0b1 2017-07-05 01:46:01 -05:00
mdipierro 8505c7b282 R-2.16.0b1 2017-07-05 01:42:59 -05:00
mdipierro 5583e9cdc7 R-2.16.0b1 2017-07-05 01:37:48 -05:00
mdipierro 5d8a25626c tracking PyDAL 17.07 2017-07-05 01:32:50 -05:00
mdipierro 9ded289924 fixed minor pylint -E errors 2017-07-02 01:55:51 -05:00
mdipierro f657b42f65 fixed undefined variable 2017-07-02 01:34:05 -05:00
mdipierro 1c0b498880 fixed undefined variable 2017-07-02 01:32:25 -05:00
mdipierro 2fd0c7c778 new dal 2017-06-30 09:22:50 -05:00
mdipierro 3d0b81cbe6 reverted backward imcompatible change 2017-06-30 08:22:26 -05:00
mdipierro 48d69e9724 Merge branch 'master' of github.com:web2py/web2py 2017-06-27 16:43:58 -05:00
mdipierro 757ce4e76e fixes #1528 2017-06-27 16:43:42 -05:00
ilvalle af7bfac1e2 Testing pypy3.5 in travis, switching to ubuntu trusty 2017-06-24 08:07:50 +02:00
ilvalle 054320820c fix performance regression with compiled views 2017-06-24 07:21:23 +02:00
Richard Vézina 1a52b0ee3b Enhance main.py PEP8 2017-06-22 09:46:55 -04:00
abastardi 78f3af6fc1 Use FormData when supported to allow Ajax file uploads 2017-06-21 16:29:19 -04:00
Richard Vézina b5b98d6e19 Close #1664 remaining py2.6 specific code 2017-06-21 15:32:28 -04:00
Richard Vézina aa1b71e431 Enhance http.py PEP8 2017-06-21 15:14:41 -04:00
Richard Vézina 472c0ff2fb Enhance html.py PEP8 2017-06-21 15:08:52 -04:00
Richard Vézina 58bedd4c1a Enhance highlight.py PEP8 2017-06-21 14:54:59 -04:00
Richard Vézina 88790dcaee Enhance globals.py PEP8 2017-06-21 14:49:10 -04:00
Richard Vézina a8fb41333b Enhance decoder.py PEP8 2017-06-21 14:38:52 -04:00
Richard Vézina 8d5464692f Enhance dal.py PEP8 2017-06-21 13:41:10 -04:00
Richard Vézina 2080e0460f Enhance contenttype.py PEP8 2017-06-21 13:32:42 -04:00
Richard Vézina 7f5fc798c5 Enhance compileapp.py PEP8 2017-06-21 13:30:02 -04:00
Richard Vézina 833cb03ee1 Enhance admin.py PEP8 2017-06-21 13:22:30 -04:00
Richard Vézina 590de9c890 Enhance validators.py PEP8 and fix docstring py3 compatibility 2017-06-21 11:57:31 -04:00
Richard Vézina 583d106104 Fix docstring py3 compatibility issues print -> print() 2017-06-21 11:33:00 -04:00
Richard Vézina 7ada2cf89a Enhance tools.py PEP8 compliancy 2017-06-21 11:27:54 -04:00
mdipierro 2f0b429f9e Merge pull request #1651 from BuhtigithuB/improve/pep8-authapi-py
Enhance authapi.py PEP8 compliancy
2017-06-20 16:44:18 -05:00
ilvalle 9f79dccb05 fix minify, added tests 2017-06-20 22:12:43 +02:00
Richard Vézina a78662e4cc Enhance authapi.py PEP8 compliancy 2017-06-20 16:02:50 -04:00
mdipierro 81fa787ec2 fixes #1514, thanks RekGRpth 2017-06-20 14:59:57 -05:00
mdipierro 159dd0d022 fixed #1542, thanks RekGRpth 2017-06-20 14:49:54 -05:00
mdipierro 16df6840ed fixes #1547, thanks yaminle 2017-06-20 14:47:59 -05:00
mdipierro 0674111129 fixes #1579, thanks Nico 2017-06-20 14:29:47 -05:00
mdipierro 18b755b8da fixed #1583, thanks matclab 2017-06-20 14:24:35 -05:00
mdipierro 2174fc3bec partially fixes #1621, thanks Nico. The strip issue is a python3 error in my opinion 2017-06-20 12:26:14 -05:00
mdipierro 77a947b35c fixed #1638, thanks Anthony 2017-06-20 12:02:40 -05:00
mdipierro 954cef48da fixes #1648, thanks you tbear 2017-06-20 11:53:47 -05:00
mdipierro 566feb79d4 Merge pull request #1649 from ilvalle/fix_globals
fix Pickler.dispatch_table
2017-06-15 04:01:14 -05:00
mdipierro c117e3d1b9 Merge pull request #1644 from leonelcamara/test_is_url_py3
Changed URL validation to use urlparse instead of regex for spliting …
2017-06-15 04:00:30 -05:00
mdipierro 0c3662fb6d scheduler should decode credentials 2017-06-15 03:59:51 -05:00
ilvalle 2ea5939640 fix Pickler.dispatch_table 2017-06-14 21:45:18 +02:00
Leonel Câmara 7c9653ae23 Merge branch 'master' into test_is_url_py3 2017-06-14 15:41:07 +01:00
mdipierro a02538549b Merge pull request #1642 from leonelcamara/restful
Fixes #1634 by adding a parameter "ignore_extension" to Request.restf…
2017-06-14 09:30:52 -05:00
mdipierro ce965cf62a Merge pull request #1641 from leonelcamara/i1639
Fixes #1639
2017-06-14 09:30:25 -05:00
Leonel Câmara 2a33c0faff Changed URL validation to use urlparse instead of regex for spliting the URL
Enabled test_is_url in Python 3 since it is now passing
This might be one of the last fixes to #1353
Fixes #1598
2017-06-07 04:59:03 +01:00
Leonel Câmara ac67beb280 Fixes #1634 by adding a parameter "ignore_extension" to Request.restful which allows you to leave the args untouched if you set it to True. 2017-06-06 01:53:42 +01:00
Leonel Câmara 617ca4a98d Fixes #1639 2017-06-05 23:52:25 +01:00
mdipierro 85ecebc3a4 Merge pull request #1640 from leonelcamara/i1628better
Fixes #1628
2017-06-05 17:44:01 -05:00
Leonel Câmara 376c12a225 Fixes #1628 2017-06-05 23:35:41 +01:00
mdipierro 7769917102 Merge pull request #1637 from timrichardson/issues/1636
fix to #1636: sqlgrid left= (left outer join) fails because db._adapt…
2017-06-05 11:48:33 -05:00
mdipierro 30c28ad44c Merge pull request #1630 from ilvalle/fix_1629
fix web debugger
2017-06-05 11:41:25 -05:00
mdipierro d905968197 Merge pull request #1626 from willimoa/issue/1472
Issue/1472
2017-06-05 11:40:38 -05:00
mdipierro f1ef95e15f Merge pull request #1625 from Scimonster/master
Fix #1624 -- Unicode in XML sanitizing causes error
2017-06-05 11:39:52 -05:00
mdipierro da6688360d Merge pull request #1623 from ilvalle/fix_tag_py3
fix TAG helper on PY3
2017-06-05 11:39:15 -05:00
Tim Richardson f30c31a8a2 fix to #1636: sqlgrid left= (left outer join) fails because db._adapter.tables() now returns dict (previously a table) 2017-06-01 16:53:04 +10:00
Andrew Willimott 59405b9f18 d3 js check was pointing to app, now checking in admin app. 2017-05-16 06:54:51 +12:00
Andrew Willimott 1c747357b0 Repointed appadmin to admin/static for d3 files 2017-05-16 06:47:30 +12:00
Andrew Willimott 6342bf0ddb Consistent set of d3 js and css across admin, examples, and welcome 2017-05-15 20:12:58 +12:00
ilvalle f76437703b fix web debugger, close #1629 2017-05-13 08:26:09 +02:00
Andrew Willimott 6f256be1f1 Added databases variable check 2017-05-08 07:02:31 +12:00
Andrew Willimott 3505e372d8 Remove graphviz graph code from appadmin and design files 2017-05-08 06:40:50 +12:00
Scimonster 49bf14e79a Fix #1624 -- Unicode in XML sanitizing causes error
Add unit test for it
2017-05-07 14:32:52 +03:00
ilvalle cf1ea98217 fix TAG helper on PY3, updated web2pyHTMLParser 2017-05-06 08:51:58 +02:00
mdipierro 8a741023d8 Merge pull request #1622 from willimoa/enhancement/d3_graph
Enhancement/d3 graph
2017-05-03 23:57:42 -05:00
mdipierro 1f4a490a84 Merge pull request #1620 from ilvalle/issue_1618
fix cron with py3, added initial tests, close #1618
2017-05-03 23:57:12 -05:00
Andrew Willimott ca5539561f Typo delete fix for hooks() return 2017-05-04 07:17:04 +12:00
Andrew Willimott 57b554d618 Initial d3 graph commit. Add d3 to graph layout 2017-05-04 07:03:07 +12:00
mdipierro baa129f871 Merge pull request #1527 from leonelcamara/authapi2
Auth refactor
2017-05-01 09:13:18 -05:00
ilvalle 90e606dcfd fix cron with py3, added initial tests, close #1618 2017-05-01 15:31:37 +02:00
mdipierro 1d77968a06 Merge pull request #1617 from ilvalle/issue_1609
fix open file in py3
2017-04-28 08:12:54 -05:00
mdipierro 1842a2e42c Merge pull request #1616 from cccaballero/py3welcome
Fixes in generic layouts for python 3 compatibility
2017-04-28 08:11:42 -05:00
mdipierro c9b6b0faf8 Merge pull request #1607 from sugizo/fix_memcache_appadmin
appadmin can use ccache with cache.ram = cache.disk = cache.memcache
2017-04-28 08:09:17 -05:00
mdipierro d8be963656 Merge pull request #1606 from cccaballero/master
Added on_succes and custom options for web2py ajax function
2017-04-28 08:08:39 -05:00
mdipierro 496112c4fe Merge pull request #1604 from ilvalle/fix_1570
prevent is_empty from stripping whitespaces, close #1570
2017-04-28 08:07:20 -05:00
mdipierro a186f5c51f Merge pull request #1603 from ilvalle/fix_1582_internazionalized_domain_names
fix IS_EMAIL for internationalized domain names
2017-04-28 08:05:30 -05:00
mdipierro fef43cd053 Merge pull request #1602 from leonelcamara/fix_webclientforget
Create a CookieJar on __init__ instead of creating a new one each post
2017-04-28 08:04:56 -05:00
mdipierro ab41cd94ec Merge pull request #1601 from leonelcamara/port_dialog
Make web2pyDialog Python 3 compatible
2017-04-28 08:04:06 -05:00
mdipierro 9ab7ed0029 catching errors in python3 in custom_import 2017-04-28 08:01:20 -05:00
ilvalle 4d117af85f fix open file in py3, close #1609 2017-04-27 18:08:59 +02:00
Carlos Cesar Caballero Díaz 60a6180a77 fixes in generic layouts for python 3 compatibility 2017-04-27 10:35:27 -04:00
sugizo ab537242c4 appadmin can use ccache with cache.ram = cache.disk = cache.memcache 2017-04-13 06:31:40 +07:00
Carlos Cesar Caballero Díaz 02d2fefc21 renamed "on_succes" option to "done" allowing multiple inputs
renamed "on_succes" option to "done" allowing multiple inputs,code
refactoring
2017-04-12 08:48:52 -04:00
Carlos Cesar Caballero Díaz 232598fd8d Added on_succes and custom options for web2py ajax function 2017-04-10 12:23:34 -04:00
ilvalle 7a69e087f7 prevent is_empty from stripping whitespaces, close #1570 2017-04-06 20:48:12 +02:00
ilvalle 6da3a9d8fd fix is_email with internationalized Domain Names, close #1582 2017-04-05 22:16:21 +02:00
Leonel Câmara fe0f506efc Create a CookieJar on __init__ instead of creating a new one each post
Fixes #1505
2017-04-03 19:54:06 +01:00
Leonel Câmara 140023e920 Make web2pyDialog Python 3 compatible
checks "port web2pyDialog" in #1353
2017-03-31 12:04:55 +01:00
mdipierro ad43249f61 Merge branch 'master' of github.com:web2py/web2py 2017-03-21 12:28:14 -05:00
mdipierro dd31fb480c Merge branch 'ilvalle-py36' 2017-03-21 12:27:29 -05:00
mdipierro e5121876db Merge branch 'py36' of https://github.com/ilvalle/web2py into ilvalle-py36 2017-03-21 12:27:11 -05:00
mdipierro 532137bce5 Merge pull request #1594 from BuhtigithuB/improve/translations
Improve french translation and close #1592
2017-03-21 12:24:44 -05:00
mdipierro 35f7caa2f0 Merge pull request #1591 from cccaballero/master
Some fixes on ldap_auth
2017-03-21 12:24:05 -05:00
mdipierro 98dddee697 Merge pull request #1578 from ndegroot/minorcleanupbs3
minor cleanup removed unused class 'navbar-ex1-collapse' see https://…
2017-03-21 12:22:48 -05:00
mdipierro 5000c47472 Merge pull request #1568 from amerikan/patch-2
Remove ! from tag
2017-03-21 12:22:16 -05:00
mdipierro bf3d53ad96 Merge pull request #1567 from amerikan/patch-3
Removed ! from tag
2017-03-21 12:21:13 -05:00
mdipierro b2f221bbaa Merge pull request #1566 from BrenBarn/flexserve
Add FlexibleService, which allows @service-style methods with varargs
2017-03-21 12:18:09 -05:00
mdipierro 868d6f6369 Merge branch 'master' of github.com:web2py/web2py 2017-03-21 12:06:11 -05:00
mdipierro 0e1831bcc7 Merge pull request #1581 from nextghost/pydal-17.01
Update SQLTABLE for API changes in PyDAL 17.01
2017-03-21 12:06:00 -05:00
mdipierro dda808ebda pydal 17.03 2017-03-21 12:05:47 -05:00
mdipierro f31c9002f7 reverted pydal 16.11 2017-03-21 00:42:02 -05:00
mdipierro 6354fbedf8 commented a test 2017-03-21 00:24:17 -05:00
mdipierro cc11a14ce3 commented a test 2017-03-21 00:21:21 -05:00
mdipierro 2fa9597cd8 added debug info 2017-03-21 00:04:55 -05:00
mdipierro c9d656ea45 added logging, thanks gi0baro 2017-03-20 18:06:23 -05:00
mdipierro 1eec01b830 pydal 17.03 2017-03-20 16:21:05 -05:00
mdipierro c10983b191 Merge branch 'master' of github.com:web2py/web2py 2017-03-20 16:15:31 -05:00
mdipierro 975ab7b923 SQLFORM.grid(showblobs=True) 2017-03-20 16:15:18 -05:00
Hardirc a81e116274 Improve french translation and close #1592 2017-03-15 01:03:21 -04:00
Carlos Cesar Caballero Díaz 34f2825a49 fix ldap_auth logging info not shown 2017-03-14 13:09:29 -04:00
Carlos Cesar Caballero Díaz 7dec909254 fixed group mapping on ldap auth 2017-03-14 13:00:32 -04:00
ilvalle ca9198a26e updated to latest dal 2017-03-07 21:11:05 +01:00
ilvalle ad421e42c6 added tests for py36 2017-03-06 21:35:23 +01:00
Martin Doucha 6954988851 Update SQLTABLE for API changes in PyDAL 17.01 2017-02-28 22:12:54 +01:00
Nico de Groot f0382d646c minor cleanup removed unused class 'navbar-ex1-collapse' see https://github.com/twbs/bootstrap/issues/10948 2017-02-26 13:21:44 +01:00
Erik Montes 48a0683dd1 Removed ! from tag 2017-02-07 10:47:26 -08:00
Erik Montes d7fb270e7e Remove ! from tag 2017-02-07 10:46:22 -08:00
BrenBarn 86a2c529b9 Change to modify Service instead of adding FlexibleService 2017-01-31 14:13:43 -08:00
BrenBarn 55592e7c6e Add FlexibleService, which allows @service-style methods that accept varargs 2017-01-31 11:48:28 -08:00
mdipierro 16f60d5cff Merge pull request #1565 from kristjanvalur/master
Update compileapp.py
2017-01-30 12:48:37 -06:00
mdipierro 930370ee91 Merge pull request #1562 from richboss/dev_urandom
don't write to /dev/urandom on Windows platforms
2017-01-30 12:47:50 -06:00
mdipierro 215abc9e4f download over https for security 2017-01-30 12:46:50 -06:00
Kristján Valur Jónsson b6e5e16526 Update compileapp.py
The "current" may have been altered by the controller.  This is the case for admin/controllers/shell.py, in callback(), where a new shell environment is created (from gluon/shell)
If this happens, the incorrect "response" is being referenced and the output of the controller is lost.
2017-01-25 20:10:03 +00:00
Richard Boß b2548f5631 don't write to /dev/urandom on Windows platforms 2017-01-22 21:05:22 +01:00
mdipierro f14c384d83 Merge pull request #1558 from abastardi/fix-router
Fix router functions check
2017-01-19 09:23:16 -06:00
mdipierro 9f186e5d5d Merge pull request #1555 from maxslimmer/master
ccache page fix for disk cache statistics value
2017-01-19 09:23:02 -06:00
mdipierro 51dd427b9e Merge pull request #1550 from senolakkas/patch-2
fix css validation error
2017-01-19 09:22:38 -06:00
abastardi 7b3fe560ed Update test_router_functions to test extensions 2017-01-14 13:14:04 -05:00
abastardi bd526452a8 Fix router functions check
When the router checks whether the requested route matches a function in the
functions list, it does not strip the extension, causing failures for URLs with extensions.
This change strips the extension before comparing to the list of functions.
2017-01-13 15:52:42 -05:00
maxslimmer d862da3543 revert part of fix to displaying cache statistics (#1) 2017-01-04 18:08:57 -08:00
maxslimmer 0165cfadef get cache.disk statistics value for update and display 2017-01-04 18:03:37 -08:00
maxslimmer 46b7716b1d get cache.disk statistics for update 2017-01-04 18:02:23 -08:00
Şenol AKKAŞ 7e5277030a fix css validation error 2016-12-26 14:21:41 +03:00
mdipierro b2e03c9dee Merge pull request #1540 from ilvalle/fix_ipaddress
Replaced ipaddr with ipaddress.py (backported from py3)
2016-12-23 21:58:13 -06:00
mdipierro 0644df5283 Merge pull request #1549 from amerikan/patch-1
typo fix
2016-12-23 21:57:56 -06:00
Erik Montes 4acd9f8f2b typo fix 2016-12-21 11:17:12 -08:00
mdipierro ce0c5f2d5a Merge branch 'master' of github.com:web2py/web2py 2016-12-20 15:49:44 -06:00
mdipierro 28b0385d9b changes to fabfile 2016-12-20 15:49:30 -06:00
mdipierro ac9bccb9a2 Merge pull request #1548 from nextghost/languages
Include Auth and Crud messages in language file updates
2016-12-20 15:42:38 -06:00
mdipierro aa5b40528f Merge pull request #1546 from wish7code/master
Fix for #1443 - PR will enable emperor.uwsgi.service on boot
2016-12-20 15:42:10 -06:00
mdipierro 96efc7bfce Merge pull request #1539 from smorrison/master
when mkdir of missing app folders, handle broken links properly
2016-12-20 15:40:30 -06:00
mdipierro aa584faed6 Merge pull request #1538 from vinyldarkscratch/master
Add web2py componentBegin event
2016-12-20 15:39:55 -06:00
Martin Doucha e7cab3b975 Add Auth and Crud messages when updating language files 2016-12-20 18:54:20 +01:00
wish7 867f93b634 Fix for #1443 - currently emperor.uwsgi.service is not started on boot,
PR will enable emperor.uwsgi.service on boot
(behaviour as in old versions of this script)
2016-12-09 17:21:04 +01:00
mdipierro efff27ffe4 improved fabfile 2016-12-07 23:02:56 -06:00
ilvalle 637579f531 replaced ipaddr with ipaddress.py (backported from py3) 2016-12-03 08:42:35 +01:00
sean 52fe4407b8 when mkdir of missing app folders, handle broken links properly 2016-12-01 10:29:23 -05:00
Vinyl Darkscratch fc0add67b5 Add web2py componentBegin event
Like the “w2p:componentComplete” event I added a while back, this is
another event that I use in my website, and I thought it would be
helpful to share it with others.  This way, you can easily catch when a
web2py component starts loading, and write a handler for whatever
reason (in my case, determining when the user is navigating to a new
page through a smooth loading function piggybacking off of the web2py
component system)
2016-11-29 14:05:17 -08:00
Vinyl Darkscratch 3d2834c81a Merge remote-tracking branch 'web2py/master' 2016-11-29 13:57:46 -08:00
Leonel Câmara a23b264a37 make sudo false again 2016-11-21 19:48:41 +00:00
mdipierro 0b8ecea4dc sudo: required, thanks Leonel 2016-11-21 10:27:16 -06:00
Leonel Câmara 79e256b1d7 require sudo 2016-11-21 15:53:10 +00:00
mdipierro 67945c2d5c Merge pull request #1533 from gchiesa/casv3
implemented base support for CASv3
2016-11-21 09:13:41 -06:00
mdipierro c600854f4b Merge pull request #1532 from vinyldarkscratch/translations
Fix T.M() not adding to language files
2016-11-21 09:12:42 -06:00
Leonel Câmara 920ab72415 fixed _update_session_user not really updating the session 2016-11-20 20:11:45 +00:00
Leonel Câmara 757d46274e fix bug with new login_user 2016-11-20 20:00:51 +00:00
Leonel Câmara 7b66ec0ae3 Fixes #1506 2016-11-20 19:51:51 +00:00
Leonel Câmara bf5ec0d7cf Fixed a long standing bug in login_user which was using 'password' instead of settings.password_field
Fixes #636
2016-11-20 19:38:21 +00:00
Giuseppe Chiesa 2c70a858f1 implemented base support for CASv3 2016-11-17 13:30:11 +01:00
mdipierro ad9ebea900 Merge pull request #1531 from vinyldarkscratch/master
Mobile (iOS) flash dismissal fix
2016-11-14 08:34:00 -06:00
mdipierro c72330f2cd Merge pull request #1530 from BMarvi/master
Update extract_mysql_models.py
2016-11-14 08:33:30 -06:00
mdipierro 091d9c74b0 Merge pull request #1525 from michele-comitini/confirm_registration_redirect_fix
keep the _next while doing the redirect
2016-11-14 08:27:39 -06:00
mdipierro bebdbd9d5e Merge pull request #1519 from matclab/fix/1518
Allow for firstname and lastname in verify_email message
2016-11-14 08:27:10 -06:00
mdipierro 05e28ddffd Merge pull request #1516 from pixelbandito/grammar-uppercase-lowercase
Corrected use of 'lowercase' and 'uppercase' to fix #1515
2016-11-14 08:26:44 -06:00
Vinyl Darkscratch e19435dbcf Prevent Firefox from loading the page "javascript:null;" 2016-11-11 18:51:38 -08:00
Vinyl Darkscratch 90ee6f3754 Add three-quote support on markmin 2016-11-11 15:51:01 -08:00
Vinyl Darkscratch d51ea90e18 Code fix to allow adding markmin translations
Playing with this file made me realize that the quotes defining the
strings themselves are added to the regex searches, and that my
previous addition caused invalid syntax.  So, I decided to fix this by
placing the first quote before the markmin decorator.  (Triple quotes
won’t work, however…)
2016-11-11 15:49:28 -08:00
Vinyl Darkscratch 3fed558bdd Merge remote-tracking branch 'web2py/master' into translations 2016-11-11 15:44:21 -08:00
Vinyl Darkscratch 870d9d3e57 Fix .DS_Store ignore 2016-11-11 13:52:43 -08:00
Vinyl Darkscratch 0a07af55f9 Replace "#" link with "javascript:null;" 2016-11-11 03:31:10 -08:00
Vinyl Darkscratch f7adfbde76 Replace "#" link with "javascript:null;" 2016-11-11 03:30:50 -08:00
Vinyl Darkscratch 75c1d80824 Replace "#" link with "javascript:null;" 2016-11-11 03:30:16 -08:00
Marvi c64a9192d4 Update extract_mysql_models.py 2016-11-11 10:12:11 +01:00
Vinyl Darkscratch 8e79e49ae3 Mobile (iOS) fix for dismissing flashes
iOS devices don’t like listening to clicks on most objects.  They
typically prefer a and button objects.  This fix replaces the
#closeflash span with a link to “#” instead (while also inheriting
text-decoration and color styling), so that mobile (iOS) devices will
allow you to close the flash.
2016-11-10 01:19:06 -08:00
Vinyl Darkscratch 46ce04355f Merge remote-tracking branch 'web2py/master' 2016-11-10 01:14:40 -08:00
Leonel Câmara 85c68e6876 typo 2016-11-05 16:52:42 +00:00
Leonel Câmara d1dfc4a06a use _compat's long 2016-11-05 16:51:54 +00:00
Leonel Câmara 02f0bdb8d3 Auth refactor, extracted many methods into a base class for more generic auth mechanisms.
Partially addresses #1526
Includes a solution for IS_LOWER and IS_UPPER validator problems I mentioned in #1353
2016-11-05 16:37:22 +00:00
Michele Comitini 6b1225da02 keep the _next while doing the redirect 2016-11-04 09:59:49 +01:00
Michele Comitini 4ea31820aa Merge pull request #1520 from michele-comitini/wrong_serializers_import
fix wrong import in default controller of admin app
2016-11-03 12:00:15 +01:00
Michele Comitini 8e1630843a fix wrong import in default controller of admin app 2016-11-02 00:41:40 +01:00
Mathieu Clabaut 2d4817841f Allow for firstname and lastname in verify_email message 2016-11-01 11:31:01 +01:00
Chris Garcia 4226b6d0e1 Corrected use of 'lowercase' and 'uppercase' to fix #1515 2016-10-28 15:23:18 -05:00
Vinyl Darkscratch 3e2b8f89aa Merge remote-tracking branch 'web2py/master' into translations 2016-10-10 20:45:42 -07:00
98 changed files with 6584 additions and 4168 deletions
+1
View File
@@ -13,6 +13,7 @@
*.orig
Thumbs.db
.DS_Store
*.DS_Store
index.yaml
routes.py
logging.conf
+13 -2
View File
@@ -1,13 +1,21 @@
language: python
sudo: false
sudo: required
cache: pip
dist: "trusty"
python:
- '2.7'
- 'pypy'
- '3.5'
- '3.6'
- 'pypy-5.3.1'
- 'pypy3.5-5.7.1-beta'
matrix:
allow_failures:
- python: 'pypy3.5-5.7.1-beta'
install:
- pip install -e .
@@ -32,3 +40,6 @@ notifications:
addons:
postgresql: "9.4"
apt:
packages:
- postgresql-9.4-postgis-2.3
+38 -7
View File
@@ -1,8 +1,29 @@
## 2.15.x
- web2py does not support python 2.6 anymore
- py3.5 syntax compatible (see #1353 for details)
- dropped web shell from admin
## 2.15.0b1
- dropped support for python 2.6
- dropped web shell
- experimental python 3 support
- experimental authapi for service login
- allow ajax file uploads
- more tests
- more pep8 compliance
- d3.js model visulization
- improved scheduler
- is_email support for internationalized Domain Names
- improved used of cookies with CookieJar
- SQLFORM.grid(showblobs=True)
- import JS events (added w2p.componentBegin event)
- added support for CASv3
- allow first_name and last_name placeholders in verify_email message
- added three-quote support in markmin
- updated pg8000 driver (but we still recommend psycopg2)
- compiled views use . separator not _ separator (must recompile code)
- better serbian, french, and catalan translations
- speed improvements (refactor of compileapp and pyc caching)
- removed web shell (never worked as intended)
- allow Expose(..., follow_symlink_out=False).
- Updated fpdf to latest version
- JWT support
- import fabfile for remote deployment
- scheduler new feature: you can now specify intervals with cron
- gluon/* removed from sys.path. Applications relying on statements like e.g.
"from storage import Storage"
@@ -10,8 +31,18 @@
"from gluon.storage import Storage"
- tests can only be run with the usual web2py.py --run_system_tests OR with
python -m unittest -v gluon.tests on the root dir
- updated pymysql driver
- jQuery 3.2.1
- PyDAL 17.07 including:
allow jsonb support for postgres
correctly configure adapters that need connection for configuration
better caching
updated IMAP adapter methods to new API
experimental suport for joinable subselects
improved Teradata support
improved mongodb support
overall refactoring
experimental support for Google Cloud SQL v2
new pymysql driver
## 2.14.6
+2 -2
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.15.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
@@ -54,7 +54,7 @@ src:
### build web2py_src.zip
echo '' > NEWINSTALL
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
mdp:
make src
+1 -1
View File
@@ -1 +1 @@
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48
Version 2.15.2-stable+timestamp.2017.07.19.01.21.31
+50 -56
View File
@@ -12,11 +12,6 @@ import gluon.contenttype
import gluon.fileutils
from gluon._compat import iteritems
try:
import pygraphviz as pgv
except ImportError:
pgv = None
is_gae = request.env.web2py_runtime_gae or False
# ## critical --- make a copy of the environment
@@ -465,6 +460,7 @@ def ccache():
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
for key in cache.disk.storage:
value = cache.disk.storage[key]
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
@@ -564,57 +560,6 @@ def table_template(table):
_cellborder=0, _cellspacing=0)
).xml()
def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict()
for tablename in db.tables:
if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel
else:
meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n,
style='filled',
color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group'])
for tablename in db.tables:
for field in db[tablename]:
f_type = field.type
if isinstance(f_type,str) and (
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
n1 = graph.get_node(tablename)
n2 = graph.get_node(referenced_table)
graph.add_edge(n1, n2, color="#4C4C4C", label='')
graph.layout()
if not request.args:
response.headers['Content-Type'] = 'image/png'
return graph.draw(format='png', prog='dot')
else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot':
return graph.string()
else:
return graph.draw(format=request.args(0), prog='dot')
def graph_model():
return dict(databases=databases, pgv=pgv)
def manage():
tables = manager_action['tables']
if isinstance(tables[0], str):
@@ -699,3 +644,52 @@ def hooks():
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
ul_main.append(ul_t)
return ul_main
# ##########################################################
# d3 based model visualizations
# ###########################################################
def d3_graph_model():
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
and also the app_admin bg_graph_model function
Create a list of table dicts, called "nodes"
"""
data = {}
nodes = []
links = []
subgraphs = dict()
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
if isinstance(f_type,str) and (
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
links.append(dict(source=tablename, target = referenced_table))
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js'))
response.files.append(URL('admin','static','js/d3_graph.js'))
return dict(databases=databases, nodes=nodes, links=links)
+2 -1
View File
@@ -6,6 +6,7 @@ import gluon.validators
import code
from gluon.debug import communicate, web_debugger, dbg_debugger
from gluon._compat import thread
from gluon.fileutils import open_file
import pydoc
@@ -54,7 +55,7 @@ def interact():
if filename:
# prevent IOError 2 on some circuntances (EAFP instead of os.access)
try:
lines = open(filename).readlines()
lines = open_file(filename, 'r').readlines()
except:
lines = ""
lines = dict([(i + 1, l) for (i, l) in enumerate(
+5 -2
View File
@@ -16,6 +16,7 @@ from gluon.tools import Config
from gluon.compileapp import find_exposed_functions
from glob import glob
from gluon._compat import iteritems, PY2, pickle, xrange, urlopen, to_bytes, StringIO, to_native
import gluon.rewrite
import shutil
import platform
@@ -249,6 +250,7 @@ def site():
db.app.insert(name=appname, owner=auth.user.id)
log_progress(appname)
session.flash = T('new application "%s" created', appname)
gluon.rewrite.load()
redirect(URL('design', args=appname))
else:
session.flash = \
@@ -266,6 +268,7 @@ def site():
new_repo = git.Repo.clone_from(form_update.vars.url, target)
session.flash = T('new application "%s" imported',
form_update.vars.name)
gluon.rewrite.load()
except git.GitCommandError as err:
session.flash = T('Invalid git repository specified.')
redirect(URL(r=request))
@@ -302,6 +305,7 @@ def site():
log_progress(appname)
session.flash = T(msg, dict(appname=appname,
digest=md5_hash(installed)))
gluon.rewrite.load()
else:
msg = 'unable to install application "%(appname)s"'
session.flash = T(msg, dict(appname=form_update.vars.name))
@@ -1861,7 +1865,6 @@ def user():
def reload_routes():
""" Reload routes.py """
import gluon.rewrite
gluon.rewrite.load()
redirect(URL('site'))
@@ -1960,7 +1963,7 @@ def git_push():
def plugins():
app = request.args(0)
from serializers import loads_json
from gluon.serializers import loads_json
if not session.plugins:
try:
rawlist = urlopen("http://www.web2pyslices.com/" +
+138 -137
View File
@@ -21,18 +21,19 @@
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'?': '?',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Cherche: **%s** fichiers',
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'A new version of web2py is available: %s': 'Une nouvelle version de web2py est disponible: %s ',
'A new version of web2py is available: Version 1.68.2 (2009-10-21 09:59:29)\n': 'Une nouvelle version de web2py est disponible: Version 1.68.2 (2009-10-21 09:59:29)\r\n',
'Abort': 'Abort',
'About': 'à propos',
'About application': "A propos de l'application",
'About': 'À propos',
'About application': "À propos de l'application",
'Accept Terms': 'Termes acceptés',
'Add breakpoint': 'Ajouter une interruption',
'additional code for your application': 'code supplémentaire pour votre application',
'Additional code for your application': 'Code additionnel pour votre application',
'Admin design page': 'Admin design page',
'Admin design page': 'Page de conception admin',
'admin disabled because no admin password': 'admin désactivée car aucun mot de passe admin',
'admin disabled because not supported on google app engine': 'admin désactivée car non prise en charge sur Google Apps engine',
'admin disabled because too many invalid login attempts': 'admin disabled because too many invalid login attempts',
@@ -41,7 +42,7 @@
'Admin language': "Language de l'admin",
'Admin versioning page': 'Admin versioning page',
'administrative interface': "interface d'administration",
'Administrator Password:': 'Mot de passe Administrateur:',
'Administrator Password:': "Mot de passe de l'administrateur:",
'An error occured, please [[reload %s]] the page': 'Une erreur cest produite, sil vous plait [[reload %s]] la page',
'and rename it (required):': 'et renommez-la (obligatoire):',
'and rename it:': 'et renommez-le:',
@@ -57,14 +58,14 @@
'application is compiled and cannot be designed': "l'application est compilée et ne peut être modifiée",
'Application name:': "Nom de l'application:",
'Application updated via git pull': 'Application updated via git pull',
'are not used': 'are not used',
'are not used yet': 'are not used yet',
'are not used': 'ne sont pas utilisé',
'are not used yet': 'ne sont pas encore utilisé',
'Are you sure you want to delete file "%s"?': 'Êtes-vous sûr de vouloir supprimer le fichier «%s»?',
'Are you sure you want to delete plugin "%s"?': 'Êtes-vous sûr de vouloir supprimer le plugin "%s"?',
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Are you sure you want to uninstall application "%s"?': "Êtes-vous sûr de vouloir désinstaller l'application «%s»?",
'Are you sure you want to upgrade web2py now?': 'Êtes-vous sûr de vouloir mettre à jour web2py maintenant?',
'Are you sure?': 'Etes vous sûr?',
'Are you sure?': 'Êtes vous sûr?',
'arguments': 'arguments',
'at char %s': 'at char %s',
'at line %s': 'at line %s',
@@ -73,13 +74,13 @@
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: les tests ne sont pas thread-safe DONC NE PAS EFFECTUER DES TESTS MULTIPLES SIMULTANÉMENT.',
'ATTENTION: you cannot edit the running application!': "ATTENTION: vous ne pouvez pas modifier l'application qui tourne!",
'Autocomplete Python Code': 'Autocomplete Python Code',
'Available databases and tables': 'Bases de données et tables disponible',
'Available databases and tables': 'Bases de données et tables disponibles',
'Available Databases and Tables': 'Available Databases and Tables',
'back': 'retour',
'Back to the plugins list': 'Retour à la liste de plugins',
'Back to wizard': 'Back to wizard',
'Basics': 'Basics',
'Begin': 'Begin',
'Begin': 'Début',
'breakpoint': 'breakpoint',
'Breakpoints': 'Breakpoints',
'breakpoints': 'breakpoints',
@@ -92,7 +93,7 @@
'Cache Keys': 'Cache Keys',
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyés',
'can be a git repo': 'can be a git repo',
'Cancel': 'Retour',
'Cancel': 'Annuler',
'Cannot be empty': 'Ne peut pas être vide',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Ne peut pas compiler: il y a des erreurs dans votre application. corriger les erreurs et essayez à nouveau.',
'Cannot compile: there are errors in your app:': 'Ne peut pas compiler: il y a des erreurs dans votre application:',
@@ -102,60 +103,60 @@
'Change admin password': 'Changer le mot de passe admin',
'change editor settings': 'change editor settings',
'Changelog': 'Changelog',
'check all': 'tout vérifier ',
'check all': 'tout sélectionner',
'Check for upgrades': 'Vérifier les mises à jour',
'Check to delete': 'Cocher pour supprimer',
'Checking for upgrades...': 'Vérification des mises à jour ... ',
'Clean': 'nettoyer',
'Clear': 'Clear',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Clear': 'Effacer',
'Clear CACHE?': 'Effacer le CACHE?',
'Clear DISK': 'Effacer le DISQUE',
'Clear RAM': 'Effacer la RAM',
'Click row to expand traceback': 'Click row to expand traceback',
'Click row to view a ticket': 'Click row to view a ticket',
'click to check for upgrades': 'Cliquez pour vérifier les mises jour',
'code': 'code',
'Code listing': 'Code listing',
'collapse/expand all': 'tout réduire/agrandir',
'Command': 'Command',
'Comment:': 'Comment:',
'Commit': 'Commit',
'Commit form': 'Commit form',
'Committed files': 'Committed files',
'Command': 'Commande',
'Comment:': 'Commentaire:',
'Commit': 'Valider',
'Commit form': 'Valider le formulaire',
'Committed files': 'Fichiers validés',
'Compile': 'compiler',
'Compile (all or nothing)': 'Compile (all or nothing)',
'Compile (skip failed views)': 'Compile (skip failed views)',
'compiled application removed': 'application compilée enlevée',
'Condition': 'Condition',
'continue': 'continue',
'continue': 'continuer',
'Controllers': 'Contrôleurs',
'controllers': 'contrôleurs',
'Count': 'Count',
'Count': 'Compte',
'Create': 'Créer',
'create file with filename:': 'créer un fichier avec nom de fichier:',
'create new application:': 'créer une nouvelle application:',
'Create new simple application': 'Créer une nouvelle application',
'Create/Upload': 'Create/Upload',
'created by': 'créé par',
'Created by:': 'Created by:',
'Created On': 'Created On',
'Created on:': 'Created on:',
'Created by:': 'Créé par:',
'Created On': 'Créé le',
'Created on:': 'Créé le:',
'crontab': 'crontab',
'Current request': 'Requête actuelle',
'Current response': 'Réponse actuelle',
'Current session': 'Session en cours',
'currently running': 'tourne actuellement',
'currently saved or': 'actuellement enregistré ou',
'data uploaded': 'données chargées',
'Database': 'Database',
'data uploaded': 'données téléversées',
'Database': 'Base de données',
'database': 'base de données',
'Database %s select': 'Database %s select',
'database %s select': 'base de données %s sélectionner',
'Database administration': 'Database administration',
'database %s select': 'base de données %s sélectionner',
'Database administration': 'Administration base de données',
'database administration': 'administration base de données',
'Database Administration (appadmin)': 'Database Administration (appadmin)',
'Date and Time': 'Date et heure',
'db': 'bdd',
'db': 'bd',
'Debug': 'Debug',
'defines tables': 'définit les tables',
'Delete': 'Supprimer',
@@ -165,7 +166,7 @@
'Delete this file (you will be asked to confirm deletion)': 'Supprimer ce fichier (on vous demandera de confirmer la suppression)',
'Delete:': 'Supprimer:',
'deleted after first hit': 'deleted after first hit',
'Demo': 'Demo',
'Demo': 'Démo',
'Deploy': 'Déployer',
'Deploy on Google App Engine': 'Déployer sur Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
@@ -176,22 +177,22 @@
'Description:': 'Description:',
'design': 'conception',
'Detailed traceback description': 'Detailed traceback description',
'details': 'details',
'details': 'détails',
'direction: ltr': 'direction: ltr',
'directory not found': 'directory not found',
'Disable': 'Disable',
'Disabled': 'Disabled',
'Disable': 'Désactiver',
'Disabled': 'Désactivé',
'disabled in demo mode': 'disabled in demo mode',
'disabled in GAE mode': 'disabled in GAE mode',
'disabled in multi user mode': 'disabled in multi user mode',
'DISK': 'DISK',
'DISK': 'DISQUE',
'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared',
'Disk Cleared': 'Disque effacé',
'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Display line numbers': 'Display line numbers',
'DO NOT use the "Pack compiled" feature.': 'DO NOT use the "Pack compiled" feature.',
'docs': 'docs',
'Docs': 'Docs',
'docs': 'documents',
'Docs': 'Documents',
'done!': 'fait!',
'Downgrade': 'Downgrade',
'Download .w2p': 'Download .w2p',
@@ -201,43 +202,43 @@
'download plugins': 'télécharger plugins',
'Download plugins from repository': 'Download plugins from repository',
'EDIT': 'MODIFIER',
'Edit': 'modifier',
'edit all': 'edit all',
'Edit': 'Modifier',
'edit all': 'tout modifier',
'Edit application': "Modifier l'application",
'edit controller': 'modifier contrôleur',
'edit controller:': 'edit controller:',
'Edit current record': 'Modifier cette entrée',
'edit views:': 'modifier vues:',
'Editing %s': 'Editing %s',
'edit controller:': 'modifier le contrôleur:',
'Edit current record': 'Modifier cet enregistrement',
'edit views:': 'modifier les vues:',
'Editing %s': 'Modifier %s',
'Editing file': 'Modifier le fichier',
'Editing file "%s"': 'Modifier le fichier "% s" ',
'Editing Language file': 'Modifier le fichier de langue',
'Editing Plural Forms File': 'Editing Plural Forms File',
'Editor': 'Editor',
'Email Address': 'Email Address',
'Enable': 'Enable',
'Editing Plural Forms File': 'Modifier le fichier du formulaire pluriel',
'Editor': 'Éditeur',
'Email Address': 'Adresse courriel',
'Enable': 'Activer',
'Enable Close-Tag': 'Enable Close-Tag',
'Enable Code Folding': 'Enable Code Folding',
'Enterprise Web Framework': 'Enterprise Web Framework',
'Error': 'Error',
'Error logs for "%(app)s"': 'Journal d\'erreurs pour "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'erreurs',
'Error ticket': "Billet d'erreur",
'Errors': 'Erreurs',
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Exception %s': 'Exception %s',
'Exception instance attributes': "Attributs d'instance Exception",
'Exit Fullscreen': 'Exit Fullscreen',
'Expand Abbreviation (html files only)': 'Expand Abbreviation (html files only)',
'export as csv file': 'export au format CSV',
'Exports:': 'Exports:',
'Exports:': 'Exportions:',
'exposes': 'expose',
'exposes:': 'expose:',
'extends': 'étend',
'failed to compile file because:': 'failed to compile file because:',
'failed to reload module': 'impossible de recharger le module',
'failed to reload module because:': 'impossible de recharger le module car:',
'File': 'File',
'File': 'Fichier',
'file "%(filename)s" created': 'fichier "%(filename)s" créé',
'file "%(filename)s" deleted': 'fichier "%(filename)s" supprimé',
'file "%(filename)s" uploaded': 'fichier "%(filename)s" chargé',
@@ -247,19 +248,19 @@
'file not found': 'file not found',
'file saved on %(time)s': 'fichier enregistré le %(time)s',
'file saved on %s': 'fichier enregistré le %s',
'filename': 'filename',
'Filename': 'Filename',
'Files added': 'Files added',
'filename': 'nom de fichier',
'Filename': 'Nom de fichier',
'Files added': 'Fichiers ajoutés',
'filter': 'filtre',
'Find Next': 'Find Next',
'Find Previous': 'Find Previous',
'Form has errors': 'Form has errors',
'Find Next': 'Trouver les suivants',
'Find Previous': 'Trouver les précédents',
'Form has errors': 'Le formulaire comporte des erreurs',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraîneront des tests [passed] .',
'GAE Email': 'GAE Email',
'GAE Output': 'GAE Output',
'GAE Password': 'GAE Password',
'Generate': 'Generate',
'GAE Password': 'Mot de passe GAE',
'Generate': 'Générer',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globals##debug',
@@ -267,15 +268,15 @@
'Google App Engine Deployment Interface': 'Google App Engine Deployment Interface',
'Google Application Id': 'Google Application Id',
'Goto': 'Goto',
'graph model': 'graph model',
'Graph Model': 'Graph Model',
'graph model': 'représentation graphique du modèle',
'Graph Model': 'Représentation graphique du modèle',
'Help': 'aide',
'here': 'here',
'here': 'ici',
'Hide/Show Translated strings': 'Hide/Show Translated strings',
'Highlight current line': 'Highlight current line',
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
'Hits': 'Hits',
'Home': 'Home',
'Home': 'Accueil',
'honored only if the expression evaluates to true': 'honored only if the expression evaluates to true',
'htmledit': 'edition html',
'If start the downgrade, be patient, it may take a while to rollback': 'If start the downgrade, be patient, it may take a while to rollback',
@@ -283,7 +284,7 @@
'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.': '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.',
'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.': "Si le rapport ci-dessus contient un numéro de ticket, cela indique une défaillance dans l'exécution du contrôleur, avant toute tentative d'exécuter les doctests. Cela est généralement dû à une erreur d'indentation ou une erreur à l'extérieur du code de la fonction.\r\nUn titre vert indique que tous les tests (si définis) sont passés. Dans ce cas, les résultats des essais ne sont pas affichées.",
'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.',
'import': 'import',
'import': 'importer',
'Import/Export': 'Importer/Exporter',
'In development, use the default Rocket webserver that is currently supported by this debugger.': 'In development, use the default Rocket webserver that is currently supported by this debugger.',
'includes': 'inclus',
@@ -311,14 +312,14 @@
'Invalid request': 'Invalid request',
'invalid table names (auth_* tables already defined)': 'invalid table names (auth_* tables already defined)',
'invalid ticket': 'ticket non valide',
'Key': 'Key',
'Key': 'Clé',
'Keyboard shortcuts': 'Keyboard shortcuts',
'kill process': 'kill process',
'language file "%(filename)s" created/updated': 'fichier de langue "%(filename)s" créé/mis à jour',
'Language files (static strings) updated': 'Fichiers de langue (chaînes statiques) mis à jour ',
'languages': 'langues',
'Languages': 'Langues',
'Last Revision': 'Last Revision',
'Last Revision': 'Dernière révision',
'Last saved on:': 'Dernière sauvegarde le:',
'License for': 'Licence pour',
'License:': 'License:',
@@ -326,8 +327,8 @@
'Line number': 'Line number',
'lists by exception': 'lists by exception',
'lists by ticket': 'lists by ticket',
'Loading...': 'Loading...',
'loading...': 'Chargement ...',
'Loading...': 'Chargement...',
'loading...': 'chargement ...',
'Local Apps': 'Local Apps',
'locals': 'locals',
'Locals##debug': 'Locals##debug',
@@ -337,7 +338,7 @@
'Login to the Administrative Interface': "Se connecter à l'interface d'administration",
'Login/Register': 'Login/Register',
'Logout': 'déconnexion',
'lost password': 'lost password',
'lost password': 'mot de passe perdu',
'Main Menu': 'Main Menu',
'Manage': 'Manage',
'Manage %(action)s': 'Manage %(action)s',
@@ -350,7 +351,7 @@
'merge': 'fusionner',
'Models': 'Modèles',
'models': 'modèles',
'Modified On': 'Modified On',
'Modified On': 'Modifié le',
'Modules': 'Modules',
'modules': 'modules',
'Multi User Mode': 'Multi User Mode',
@@ -363,8 +364,8 @@
'New Record': 'Nouvelle Entrée',
'new record inserted': 'nouvelle entrée insérée',
'New simple application': 'Nouvelle application simple',
'next': 'next',
'next %s rows': 'next %s rows',
'next': 'suivant',
'next %s rows': '%s lignes suivantes',
'next 100 rows': '100 lignes suivantes',
'NO': 'NON',
'no changes': 'no changes',
@@ -374,8 +375,8 @@
'no package selected': 'no package selected',
'no permission to uninstall "%s"': 'no permission to uninstall "%s"',
'Node:': 'Node:',
'Not Authorized': 'Not Authorized',
'Not supported': 'Not supported',
'Not Authorized': 'Pas autori',
'Not supported': 'Pas supporté',
'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.': '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.',
'Number of entries: **%s**': 'Number of entries: **%s**',
"On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": "On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.",
@@ -402,14 +403,14 @@
'Peeking at file': 'Jeter un oeil au fichier',
'Permission': 'Permission',
'Permissions': 'Permissions',
'Please': 'Please',
'Please': 'SVP',
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" supprimé',
'Plugin "%s" in application': 'Plugin "%s" dans l\'application',
'plugin not specified': 'plugin not specified',
'Plugin page': 'Plugin page',
'plugins': 'plugins',
'Plugins': 'Plugins',
'plugins': 'plugiciels',
'Plugins': 'Plugiciels',
'Plural Form #%s': 'Plural Form #%s',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Propulsé par',
@@ -423,7 +424,7 @@
'Pull': 'Pull',
'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull failed, certain files could not be checked out. Check logs for details.',
'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.',
'Push': 'Push',
'Push': 'Pousser',
'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.',
'pygraphviz library not found': 'pygraphviz library not found',
'PythonAnywhere Apps': 'PythonAnywhere Apps',
@@ -433,9 +434,9 @@
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Cleared',
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Rapid Search': 'Rapid Search',
'Record': 'Record',
'record': 'entrée',
'Rapid Search': 'Recherche rapide',
'Record': 'Enregistrement',
'record': 'enregistrement',
'record does not exist': "l'entrée n'existe pas",
'record id': 'id entrée',
'Record id': 'Record id',
@@ -444,24 +445,24 @@
'Reload routes': 'Reload routes',
'Remove compiled': 'retirer compilé',
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
'Replace': 'Replace',
'Replace All': 'Replace All',
'Replace': 'Remplacer',
'Replace All': 'Tout remplacer',
'Repository (%s)': 'Repository (%s)',
'request': 'request',
'requires distutils, but not installed': 'requires distutils, but not installed',
'requires python-git, but not installed': 'requires python-git, but not installed',
'Resolve Conflict file': 'Résoudre les conflits de fichiers',
'response': 'response',
'restart': 'restart',
'restart': 'redémarrer',
'restore': 'restaurer',
'return': 'return',
'Revert': 'Revert',
'revert': 'revenir',
'return': 'retour',
'Revert': "Revenir vers l'arrière",
'revert': "revenir vers l'arrière",
'reverted to revision %s': 'reverted to revision %s',
'Revision %s': 'Revision %s',
'Revision:': 'Revision:',
'Role': 'Role',
'Roles': 'Roles',
'Revision %s': 'Révision %s',
'Revision:': 'Révision:',
'Role': 'Rôle',
'Roles': 'Rôles',
'Rows in Table': 'Rows in Table',
'Rows in table': 'Lignes de la table',
'Rows selected': 'Lignes sélectionnées',
@@ -470,15 +471,15 @@
'Run tests in this file': 'Run tests in this file',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Lancer les tests dans ce fichier (pour lancer tous les fichiers, vous pouvez également utiliser le bouton nommé 'test')",
'Running on %s': 'Running on %s',
'Save': 'Enregistrer',
'save': 'sauver',
'Save file:': 'Save file:',
'Save file: %s': 'Save file: %s',
'Save model as...': 'Save model as...',
'Save via Ajax': 'Save via Ajax',
'Save': 'Sauvegarder',
'save': 'sauvegarder',
'Save file:': 'Sauvegarder le fichier:',
'Save file: %s': 'Sauvegarder le fichier : %s',
'Save model as...': 'Sauvegarder le modèle sous...',
'Save via Ajax': 'Sauvegarder via Ajax',
'Saved file hash:': 'Hash du Fichier enregistré:',
'Screenshot %s': 'Screenshot %s',
'Search': 'Search',
'Screenshot %s': "Capture d'écran %s",
'Search': 'Rechercher',
'Searching: **%s** %%{file}': 'Searching: **%s** %%{file}',
'Select Files to Package': 'Select Files to Package',
'selected': 'sélectionnés',
@@ -491,34 +492,34 @@
'Showing %s to %s of %s %s found': 'Showing %s to %s of %s %s found',
'Singular Form': 'Singular Form',
'Site': 'Site',
'Size of cache:': 'Size of cache:',
'Size of cache:': 'Taille de la mémoire cache:',
'skip to generate': 'skip to generate',
'some files could not be removed': 'certains fichiers ne peuvent pas être supprimés',
'Something went wrong please wait a few minutes before retrying': 'Something went wrong please wait a few minutes before retrying',
'Sorry, could not find mercurial installed': 'Sorry, could not find mercurial installed',
'source : db': 'source : db',
'source : filesystem': 'source : filesystem',
'Start a new app': 'Start a new app',
'Start searching': 'Start searching',
'source : filesystem': 'source : système de fichier',
'Start a new app': 'Commencer une nouvelle application',
'Start searching': 'Débuté la recherche',
'Start wizard': "Démarrer l'assistant",
'state': 'état',
'Static': 'Static',
'static': 'statiques',
'Static': 'Statique',
'static': 'statique',
'Static files': 'Fichiers statiques',
'Statistics': 'Statistics',
'Step': 'Step',
'step': 'step',
'stop': 'stop',
'stop': 'arrêt',
'submit': 'envoyer',
'Submit': 'Submit',
'successful': 'successful',
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet? ',
'switch to : db': 'switch to : db',
'Submit': 'Envoyer',
'successful': 'réussi',
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet?',
'switch to : db': 'transférer dans : db',
'switch to : filesystem': 'switch to : filesystem',
'Tab width (# characters)': 'Tab width (# characters)',
'table': 'table',
'Table': 'Table',
'Temporary': 'Temporary',
'Temporary': 'Temporaire',
'test': 'tester',
'Testing application': "Test de l'application",
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "requête" est une condition comme "db.table1.field1==\'value\'". Quelque chose comme "db.table1.field1==db.table2.field2" aboutit à un JOIN SQL.',
@@ -530,14 +531,14 @@
'The data representation, define database tables and sets': 'La représentation des données, définir les tables et ensembles de la base de données',
'The presentations layer, views are also known as templates': 'Les couches de présentation, les vues sont également appelées modples',
'the presentations layer, views are also known as templates': 'la couche de présentation, les vues sont également appelées modèles',
'Theme': 'Theme',
'Theme': 'Thème',
'There are no controllers': "Il n'y a pas de contrôleurs",
'There are no models': "Il n'y a pas de modèles",
'There are no modules': "Il n'y a pas de modules",
'There are no plugins': "Il n'y a pas de plugins",
'There are no private files': 'There are no private files',
'There are no private files': "Il n'y a pas de fichiers privés",
'There are no static files': "Il n'y a pas de fichiers statiques",
'There are no translators': 'There are no translators',
'There are no translators': "Il n'y a pas de traducteurs",
'There are no translators, only default language is supported': "Il n'y a pas de traducteurs, seule la langue par défaut est prise en charge",
'There are no views': "Il n'y a pas de vues",
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
@@ -552,9 +553,9 @@
'this page to see if a breakpoint was hit and debug interaction is required.': 'this page to see if a breakpoint was hit and debug interaction is required.',
'This will pull changes from the remote repo for application "%s"?': 'This will pull changes from the remote repo for application "%s"?',
'This will push changes to the remote repo for application "%s".': 'This will push changes to the remote repo for application "%s".',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'Ticket Missing': 'Ticket Missing',
'Ticket': 'Billet',
'Ticket ID': 'Identifiant du Billet',
'Ticket Missing': 'Billet manquant',
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
'TM': 'MD',
'to previous version.': 'à la version précédente.',
@@ -569,8 +570,8 @@
'Translation strings for the application': "Chaînes de traduction pour l'application",
'try': 'essayer',
'try something like': 'essayez quelque chose comme',
'Try the mobile interface': 'Try the mobile interface',
'try view': 'try view',
'Try the mobile interface': "Essayer l'interface pour mobile",
'try view': 'essayer la vue',
'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Type PDB debugger command 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': 'Impossible de vérifier les mises à jour',
@@ -595,25 +596,25 @@
'update': 'mettre à jour',
'update all languages': 'mettre à jour toutes les langues',
'Update:': 'Mise à jour:',
'Upgrade': 'Upgrade',
'Upgrade': 'Mese à jour de version',
'upgrade now': 'mettre à jour maintenant',
'upgrade now to %s': 'upgrade now to %s',
'upgrade now to %s': 'mettre à jour maintenant à %s',
'upgrade web2py now': 'mettre à jour web2py maintenant',
'upload': 'charger',
'Upload': 'Upload',
'upload': 'téléversé',
'Upload': 'Téléversé',
'Upload & install packed application': "Charger & installer l'application empaquetée",
'Upload a package:': 'Charger un paquet:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': "charger l'application:",
'Upload existing application': 'Charger une application existante',
'upload file:': 'charger le fichier:',
'Upload a package:': 'Téléverser un paquet:',
'Upload and install packed application': 'Téléversement et installation du paquet applicatif',
'upload application:': "téléverser l'application:",
'Upload existing application': 'Téléverser une application existante',
'upload file:': 'téléverser le fichier:',
'upload plugin file:': 'charger fichier plugin:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilisez (...)&(...) pour AND, (...)|(...) pour OR, et ~(...) pour NOT afin de construire des requêtes plus complexes. ',
'Use an url:': 'Utiliser une url:',
'user': 'utilisateur',
'User': 'User',
'Username': 'Username',
'Users': 'Users',
'User': 'Utilisateur',
'Username': "Nom d'utilisateur",
'Users': 'Utilisateurs',
'Using the shell may lock the database to other users of this app.': 'Using the shell may lock the database to other users of this app.',
'variables': 'variables',
'Version': 'Version',
@@ -625,9 +626,9 @@
'Warning!': 'Warning!',
'WARNING:': 'WARNING:',
'WARNING: The following views could not be compiled:': 'WARNING: The following views could not be compiled:',
'Web Framework': 'Framework Web',
'web2py Admin Password': 'web2py Admin Password',
'web2py apps to deploy': 'web2py apps to deploy',
'Web Framework': 'Cadre Web',
'web2py Admin Password': 'Mot de passe admin web2py',
'web2py apps to deploy': 'applications web2py à deployer',
'web2py Debugger': 'web2py Debugger',
'web2py downgrade': 'web2py downgrade',
'web2py is up to date': 'web2py est à jour',
@@ -635,10 +636,10 @@
'web2py Recent Tweets': 'Tweets récents sur web2py ',
'web2py upgrade': 'web2py upgrade',
'web2py upgraded; please restart it': 'web2py mis à jour; veuillez le redémarrer',
'Working...': 'Working...',
'Working...': 'Travail en cours...',
'WSGI reference name': 'WSGI reference name',
'YES': 'OUI',
'Yes': 'Yes',
'Yes': 'Oui',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button',
'You can inspect variables using the console below': 'You can inspect variables using the console below',
'You have one more login attempt before you are locked out': 'You have one more login attempt before you are locked out',
+2 -2
View File
@@ -352,9 +352,9 @@
'modules': 'modules',
'Multi User Mode': 'Multi User Mode',
'Must include at least %s %s': 'Moet ten minste bevatten %s %s',
'Must include at least %s lower case': 'Moet ten minste bevatten %s lower case',
'Must include at least %s lowercase': 'Moet ten minste bevatten %s kleine letter',
'Must include at least %s of the following : %s': 'Moet ten minste bevatten %s van het volgende : %s',
'Must include at least %s upper case': 'Moet ten minste bevatten %s upper case',
'Must include at least %s uppercase': 'Moet ten minste bevatten %s hoofdletter',
'new application "%s" created': 'nieuwe applicatie "%s" gemaakt',
'new application "%s" imported': 'new application "%s" imported',
'New Application Wizard': 'Nieuwe Applicatie Wizard',
+2 -2
View File
@@ -356,9 +356,9 @@
'modules': 'модулі',
'Multi User Mode': 'Multi User Mode',
'Must include at least %s %s': 'Має вміщувати щонайменше %s %s',
'Must include at least %s lower case': 'Повинен включати щонайменше %s малих букв',
'Must include at least %s lowercase': 'Повинен включати щонайменше %s малих букв',
'Must include at least %s of the following : %s': 'Має включати не менше %s таких символів : %s',
'Must include at least %s upper case': 'Повинен включати щонайменше %s великих букв',
'Must include at least %s uppercase': 'Повинен включати щонайменше %s великих букв',
'new application "%s" created': 'новий додаток "%s" створено',
'new application "%s" imported': 'new application "%s" imported',
'New Application Wizard': 'Майстер створення нового додатку',
+1 -1
View File
@@ -52,7 +52,7 @@ def verify_password(password):
if DEMO_MODE:
ret = True
elif not _config.get('password'):
ret - False
ret = False
elif _config['password'].startswith('pam_user:'):
session.pam_user = _config['password'][9:].strip()
import gluon.contrib.pam
+3 -3
View File
@@ -314,8 +314,8 @@
// Vim does not support modifier only keys.
return false;
}
// TODO: Current bindings expect the character to be lower case, but
// it looks like vim key notation uses upper case.
// TODO: Current bindings expect the character to be lowercase, but
// it looks like vim key notation uses uppercase.
if (isUpperCase(lastPiece)) {
pieces[pieces.length - 1] = lastPiece.toLowerCase();
}
@@ -3644,7 +3644,7 @@
* Extract the regular expression from the query and return a Regexp object.
* Returns null if the query is blank.
* If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
* If smartCase is passed in, and the query contains upper case letters,
* If smartCase is passed in, and the query contains uppercase letters,
* then ignoreCase is overridden, and the 'i' flag will not be set.
* If the query contains the /i in the flag part of the regular expression,
* then both ignoreCase and smartCase are ignored, and 'i' will be passed
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
.node {fill: steelblue;
stroke: #636363;
stroke-width: 1px;}
.auth {fill: lightgrey;}
.table {r: 10;}
.link {stroke: #bbbbbb;
stroke-width: 2px;}
td {padding: 4px;}
div.tooltip {
position: absolute;
text-align: left;
/* width: 140px; */
/* height: 28px;*/
padding: 0px 5px 0px 5px;
padding-top: 0px;
font: 12px sans-serif;
background: #fff7bc;
border: solid 1px #aaa;
border-radius: 6px;
pointer-events: none;}
h5 { font: 14px sans-serif;
background : #ec7014;
color: #ffffe5;
padding: 5px 2px 5px 2px;
margin-top: 1px;}
path {
fill: #aaaaaa;}
File diff suppressed because one or more lines are too long
+181
View File
@@ -0,0 +1,181 @@
function d3_graph() {
// Some reference links:
// How to get link ids instead of index
// http://stackoverflow.com/questions/23986466/d3-force-layout-linking-nodes-by-name-instead-of-index
// embedding web2py in d3
// http://stackoverflow.com/questions/34326343/embedding-d3-js-graph-in-a-web2py-bootstrap-page
// nodes and links are defined in appadmin.html <script>
var edges = [];
links.forEach(function(e) {
var sourceNode = nodes.filter(function(n) {
return n.name === e.source;
})[0],
targetNode = nodes.filter(function(n) {
return n.name === e.target;
})[0];
edges.push({
source: sourceNode,
target: targetNode,
value: 1});
});
edges.forEach(function(e) {
if (!e.source["linkcount"]) e.source["linkcount"] = 0;
if (!e.target["linkcount"]) e.target["linkcount"] = 0;
e.source["linkcount"]++;
e.target["linkcount"]++;
});
//var width = 960, height = 600;
var height = window.innerHeight|| docEl.clientHeight|| bodyEl.clientHeight;
var width = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
var svg = d3.select("#vis").append("svg")
.attr("width", width)
.attr("height", height);
// updated for d3 v4.
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(strength))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide(35));
// Node charge strength. Repel strength greater for less links.
//function strength(d) { return -50/d["linkcount"] ; }
function strength(d) { return -25 ; }
// Link distance. Distance increases with number of links at source and target
function distance(d) { return (60 + (d.source["linkcount"] * d.target["linkcount"])) ; }
// Link strength. Strength is less for highly connected nodes (move towards target dist)
function strengthl(d) { return 5/(d.source["linkcount"] + d.target["linkcount"]) ; }
simulation
.nodes(nodes)
.on("tick", tick);
simulation.force("link")
.links(edges)
.distance(distance)
.strength(strengthl);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 25) // Moves the arrow head out, allow for radius
.attr("refY", 0) // -1.5
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = svg.selectAll('.link')
.data(edges)
.enter().append('line')
.attr("class", "link")
.attr("marker-end", "url(#end)");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", function(d) { return "node " + d.type;})
.attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")"})
.classed("auth", function(d) { return (d.name.startsWith("auth") ? true : false);});
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// add the nodes
node.append('circle')
.attr('r', 16)
;
// add text
node.append("text")
.attr("x", 12)
.attr("dy", "-1.1em")
.text(function(d) {return d.name;});
node.on("mouseover", function(d) {
var g = d3.select(this); // the node (table)
// tooltip
var fields = d.fields;
var fieldformat = "<TABLE>";
fields.forEach(function(d) {
fieldformat += "<TR><TD><B>"+ d.name+"</B></TD><TD>"+ d.type+"</TD><TD>"+ d.disp+"</TD></TR>";
});
fieldformat += "</TABLE>";
var tiplength = d.fields.length;
// Define 'div' for tooltips
var div = d3.select("body").append("div") // declare the tooltip div
.attr("class", "tooltip") // apply the 'tooltip' class
.style("opacity", 0)
.html('<h5>' + d.name + '</h5>' + fieldformat)
.style("left", 20 + (d3.event.pageX) + "px")// or just (d.x + 50 + "px")
.style("top", tooltop(tiplength))// or ...
.transition()
.duration(800)
.style("opacity", 0.9);
});
function tooltop(tiplength) {
//aim to ensure tooltip is fully visible whenver possible
return (Math.max(d3.event.pageY - 20 - (tiplength * 14),0)) + "px"
}
node.on("mouseout", function(d) {
d3.select("body").select('div.tooltip').remove();
});
// instead of waiting for force to end with : force.on('end', function()
// use .on("tick", instead. Here is the tick function
function tick() {
node.attr('transform', function(d) {
d.x = Math.max(30, Math.min(width - 16, d.x));
d.y = Math.max(30, Math.min(height - 16, d.y));
return "translate(" + d.x + "," + d.y + ")"; });
link.attr('x1', function(d) {return d.source.x;})
.attr('y1', function(d) {return d.source.y;})
.attr('x2', function(d) {return d.target.x;})
.attr('y2', function(d) {return d.target.y;});
};
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
};
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
};
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
};
};
File diff suppressed because one or more lines are too long
+43 -11
View File
@@ -38,8 +38,12 @@
if (value > 0) $('#' + id).hide().fadeIn('slow');
else $('#' + id).show().fadeOut('slow');
},
ajax: function (u, s, t) {
ajax: function (u, s, t, options) {
/*simple ajax function*/
// set options default value
options = typeof options !== 'undefined' ? options : {};
var query = '';
if (typeof s == 'string') {
var d = $(s).serialize();
@@ -59,18 +63,44 @@
query = pcs.join('&');
}
}
$.ajax({
// default success action
var success_function = function (msg) {
if (t) {
if (t == ':eval') eval(msg);
else if (typeof t == 'string') $('#' + t).html(msg);
else t(msg);
}
};
// declare success actions as array
var success = [success_function];
// add user success actions
if ($.isArray(options.done)){
success = $.merge(success, options.done);
} else {
success.push(options.done);
}
// default jquery ajax options
var ajax_options = {
type: 'POST',
url: u,
data: query,
success: function (msg) {
if (t) {
if (t == ':eval') eval(msg);
else if (typeof t == 'string') $('#' + t).html(msg);
else t(msg);
}
}
});
success: success
};
//remove custom "done" option if exists
delete options.done;
// merge default ajax options with user custom options
for (var attrname in options) {
ajax_options[attrname] = options[attrname];
}
// call ajax function
$.ajax(ajax_options);
},
ajax_fields: function (target) {
/*
@@ -168,7 +198,8 @@
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.w2p_flash', function () {
doc.on('click', '.w2p_flash', function (event) {
event.preventDefault();
var t = $(this);
if (t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
@@ -316,6 +347,7 @@
'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target);
web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
},
'success': function (data, status, xhr) {
+13 -21
View File
@@ -233,30 +233,22 @@
<div class="clear"></div>
{{pass}}
{{if request.function=='graph_model':}}
{{if request.function=='d3_graph_model':}}
<h2>{{=T("Graph Model")}}</h2>
{{if not pgv:}}
{{=T('pygraphviz library not found')}}
{{elif not databases:}}
{{if not databases:}}
{{=T("No databases in this application")}}
{{else:}}
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-download"></i> {{=T('Save model as...')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul>
</div>
<br />
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{else:}}
<div id="vis"></div>
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
<script>
// Define the d3 input data
{{from gluon.serializers import json }}
var nodes = {{=XML(json(nodes))}};
var links = {{=XML(json(links))}};
d3_graph();
</script>
{{pass}}
{{pass}}
{{pass}}
{{if request.function == 'manage':}}
<h2>{{=heading}}</h2>
@@ -34,7 +34,7 @@
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.3.1.min.css')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
{{include 'web2py_ajax.html'}}
+3 -1
View File
@@ -105,7 +105,9 @@ def deletefile(arglist, vars={}):
{{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'))}}
{{if os.access(os.path.join(request.folder,'..','admin','static','js','d3_graph.js'),os.R_OK):}}
{{=button(URL(a=app, c='appadmin',f='d3_graph_model'), T('graph model'))}}
{{pass}}
</div>
<ul class="unstyled act_edit">
{{for m in models:}}
+1 -1
View File
@@ -46,7 +46,7 @@
</td>
<td>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<a class="btn dropdown-toggle" data-toggle="dropdown">
{{=T('Manage')}}
<span class="caret"></span>
</a>
+1 -1
View File
@@ -7,7 +7,7 @@ It is used as default when a view is not provided for your controllers
"""}}
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
{{if len(response._vars)==1:}}
{{=BEAUTIFY(response._vars.values()[0])}}
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
{{elif len(response._vars)>1:}}
{{=BEAUTIFY(response._vars)}}
{{pass}}
@@ -34,7 +34,7 @@
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.3.1.min.css')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
{{include 'web2py_ajax.html'}}
+50 -56
View File
@@ -12,11 +12,6 @@ import gluon.contenttype
import gluon.fileutils
from gluon._compat import iteritems
try:
import pygraphviz as pgv
except ImportError:
pgv = None
is_gae = request.env.web2py_runtime_gae or False
# ## critical --- make a copy of the environment
@@ -465,6 +460,7 @@ def ccache():
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
for key in cache.disk.storage:
value = cache.disk.storage[key]
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
@@ -564,57 +560,6 @@ def table_template(table):
_cellborder=0, _cellspacing=0)
).xml()
def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict()
for tablename in db.tables:
if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel
else:
meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n,
style='filled',
color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group'])
for tablename in db.tables:
for field in db[tablename]:
f_type = field.type
if isinstance(f_type,str) and (
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
n1 = graph.get_node(tablename)
n2 = graph.get_node(referenced_table)
graph.add_edge(n1, n2, color="#4C4C4C", label='')
graph.layout()
if not request.args:
response.headers['Content-Type'] = 'image/png'
return graph.draw(format='png', prog='dot')
else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot':
return graph.string()
else:
return graph.draw(format=request.args(0), prog='dot')
def graph_model():
return dict(databases=databases, pgv=pgv)
def manage():
tables = manager_action['tables']
if isinstance(tables[0], str):
@@ -699,3 +644,52 @@ def hooks():
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
ul_main.append(ul_t)
return ul_main
# ##########################################################
# d3 based model visualizations
# ###########################################################
def d3_graph_model():
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
and also the app_admin bg_graph_model function
Create a list of table dicts, called "nodes"
"""
data = {}
nodes = []
links = []
subgraphs = dict()
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
if isinstance(f_type,str) and (
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
links.append(dict(source=tablename, target = referenced_table))
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js'))
response.files.append(URL('admin','static','js/d3_graph.js'))
return dict(databases=databases, nodes=nodes, links=links)
File diff suppressed because one or more lines are too long
+43 -11
View File
@@ -38,8 +38,12 @@
if (value > 0) $('#' + id).hide().fadeIn('slow');
else $('#' + id).show().fadeOut('slow');
},
ajax: function (u, s, t) {
ajax: function (u, s, t, options) {
/*simple ajax function*/
// set options default value
options = typeof options !== 'undefined' ? options : {};
var query = '';
if (typeof s == 'string') {
var d = $(s).serialize();
@@ -59,18 +63,44 @@
query = pcs.join('&');
}
}
$.ajax({
// default success action
var success_function = function (msg) {
if (t) {
if (t == ':eval') eval(msg);
else if (typeof t == 'string') $('#' + t).html(msg);
else t(msg);
}
};
// declare success actions as array
var success = [success_function];
// add user success actions
if ($.isArray(options.done)){
success = $.merge(success, options.done);
} else {
success.push(options.done);
}
// default jquery ajax options
var ajax_options = {
type: 'POST',
url: u,
data: query,
success: function (msg) {
if (t) {
if (t == ':eval') eval(msg);
else if (typeof t == 'string') $('#' + t).html(msg);
else t(msg);
}
}
});
success: success
};
//remove custom "done" option if exists
delete options.done;
// merge default ajax options with user custom options
for (var attrname in options) {
ajax_options[attrname] = options[attrname];
}
// call ajax function
$.ajax(ajax_options);
},
ajax_fields: function (target) {
/*
@@ -168,7 +198,8 @@
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.w2p_flash', function () {
doc.on('click', '.w2p_flash', function (event) {
event.preventDefault();
var t = $(this);
if (t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
@@ -316,6 +347,7 @@
'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target);
web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
},
'success': function (data, status, xhr) {
+13 -21
View File
@@ -233,30 +233,22 @@
<div class="clear"></div>
{{pass}}
{{if request.function=='graph_model':}}
{{if request.function=='d3_graph_model':}}
<h2>{{=T("Graph Model")}}</h2>
{{if not pgv:}}
{{=T('pygraphviz library not found')}}
{{elif not databases:}}
{{if not databases:}}
{{=T("No databases in this application")}}
{{else:}}
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-download"></i> {{=T('Save model as...')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul>
</div>
<br />
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{else:}}
<div id="vis"></div>
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
<script>
// Define the d3 input data
{{from gluon.serializers import json }}
var nodes = {{=XML(json(nodes))}};
var links = {{=XML(json(links))}};
d3_graph();
</script>
{{pass}}
{{pass}}
{{pass}}
{{if request.function == 'manage':}}
<h2>{{=heading}}</h2>
@@ -17,10 +17,10 @@
<tbody>
<tr>
<td>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.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>
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_win.zip">For Windows</a>
</td>
<td>
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
@@ -28,19 +28,19 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.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>
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
</td>
<td></td>
</tr>
<tr>
<td>
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.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>
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded red" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a>
+1 -1
View File
@@ -7,7 +7,7 @@ It is used as default when a view is not provided for your controllers
"""}}
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
{{if len(response._vars)==1:}}
{{=BEAUTIFY(response._vars.values()[0])}}
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
{{elif len(response._vars)>1:}}
{{=BEAUTIFY(response._vars)}}
{{pass}}
+1 -1
View File
@@ -1 +1 @@
{{response.headers['web2py-response-flash']=response.flash}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
{{response.headers['web2py-response-flash']=response.flash}}{{if len(response._vars)==1:}}{{=response._vars[next(iter(response._vars))]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
+50 -56
View File
@@ -12,11 +12,6 @@ import gluon.contenttype
import gluon.fileutils
from gluon._compat import iteritems
try:
import pygraphviz as pgv
except ImportError:
pgv = None
is_gae = request.env.web2py_runtime_gae or False
# ## critical --- make a copy of the environment
@@ -465,6 +460,7 @@ def ccache():
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
for key in cache.disk.storage:
value = cache.disk.storage[key]
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
@@ -564,57 +560,6 @@ def table_template(table):
_cellborder=0, _cellspacing=0)
).xml()
def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict()
for tablename in db.tables:
if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel
else:
meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n,
style='filled',
color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group'])
for tablename in db.tables:
for field in db[tablename]:
f_type = field.type
if isinstance(f_type,str) and (
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
n1 = graph.get_node(tablename)
n2 = graph.get_node(referenced_table)
graph.add_edge(n1, n2, color="#4C4C4C", label='')
graph.layout()
if not request.args:
response.headers['Content-Type'] = 'image/png'
return graph.draw(format='png', prog='dot')
else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot':
return graph.string()
else:
return graph.draw(format=request.args(0), prog='dot')
def graph_model():
return dict(databases=databases, pgv=pgv)
def manage():
tables = manager_action['tables']
if isinstance(tables[0], str):
@@ -699,3 +644,52 @@ def hooks():
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
ul_main.append(ul_t)
return ul_main
# ##########################################################
# d3 based model visualizations
# ###########################################################
def d3_graph_model():
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
and also the app_admin bg_graph_model function
Create a list of table dicts, called "nodes"
"""
data = {}
nodes = []
links = []
subgraphs = dict()
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
if isinstance(f_type,str) and (
f_type.startswith('reference') or
f_type.startswith('list:reference')):
referenced_table = f_type.split()[1].split('.')[0]
links.append(dict(source=tablename, target = referenced_table))
nodes.append(dict(name=tablename, type="table", fields = fields))
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
response.files.append(URL('admin','static','js/d3.min.js'))
response.files.append(URL('admin','static','js/d3_graph.js'))
return dict(databases=databases, nodes=nodes, links=links)
+73 -61
View File
@@ -3,8 +3,8 @@
'!langcode!': 'fr-ca',
'!langname!': 'Français (Canadien)',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN',
'%s %%{row} deleted': '%s rangées supprimées',
'%s %%{row} updated': '%s rangées mises à jour',
'%s %%{row} deleted': '%s lignes supprimées',
'%s %%{row} updated': '%s lignes mises à jour',
'%s selected': '%s sélectionné',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
@@ -13,12 +13,13 @@
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'?': '?',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'about': 'à propos',
'About': 'À propos',
'Access Control': "Contrôle d'accès",
'admin': 'admin',
'Administrative Interface': 'Administrative Interface',
'Administrative Interface': "Interface d'administration",
'Administrative interface': "Interface d'administration",
'Ajax Recipes': 'Recettes Ajax',
'An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
@@ -37,37 +38,39 @@
'change password': 'changer le mot de passe',
'Check to delete': 'Cliquez pour supprimer',
'Check to delete:': 'Cliquez pour supprimer:',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Clear CACHE?': 'Vider le CACHE?',
'Clear DISK': 'Vider le DISQUE',
'Clear RAM': 'Vider la RAM',
'Client IP': 'IP client',
'Community': 'Communauté',
'Components and Plugins': 'Components and Plugins',
'Components and Plugins': 'Composants et Plugiciels',
'Config.ini': 'Config.ini',
'Controller': 'Contrôleur',
'Copyright': "Droit d'auteur",
'Created By': 'Créé par',
'Created On': 'Créé le',
'Current request': 'Demande actuelle',
'Current response': 'Réponse actuelle',
'Current session': 'Session en cours',
'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées',
'Database': 'base de données',
'Database %s select': 'base de données %s select',
'Database %s select': 'base de données %s selectionnée',
'Database Administration (appadmin)': 'Database Administration (appadmin)',
'db': 'db',
'DB Model': 'Modèle DB',
'DB Model': 'Modèle BD',
'Delete:': 'Supprimer:',
'Demo': 'Démo',
'Deployment Recipes': 'Recettes de déploiement ',
'Description': 'Descriptif',
'Deployment Recipes': 'Recettes de déploiement',
'Description': 'Description',
'design': 'design',
'Design': 'Design',
'DISK': 'DISK',
'DISK': 'DISQUE',
'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared',
'Disk Cleared': 'Disque vidé',
'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Documentation': 'Documentation',
"Don't know what to do?": "Don't know what to do?",
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
'done!': 'fait!',
'Download': 'Téléchargement',
'E-mail': 'Courriel',
@@ -75,26 +78,27 @@
'Edit current record': "Modifier l'enregistrement courant",
'edit profile': 'modifier le profil',
'Edit This App': 'Modifier cette application',
'Email and SMS': 'Email and SMS',
'Email and SMS': 'Courriel et texto',
'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': 'entrer un entier compris entre %(min)g et %(max)g',
'Errors': 'Erreurs',
'export as csv file': 'exporter sous forme de fichier csv',
'FAQ': 'faq',
'FAQ': 'FAQ',
'First name': 'Prénom',
'Forms and Validators': 'Formulaires et Validateurs',
'Free Applications': 'Applications gratuites',
'Function disabled': 'Fonction désactivée',
'Graph Model': 'Graph Model',
'Graph Model': 'Représentation graphique du modèle',
'Group %(group_id)s created': '%(group_id)s groupe créé',
'Group ID': 'Groupe ID',
'Group ID': 'ID du groupe',
'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s",
'Groups': 'Groupes',
'Hello World': 'Bonjour le monde',
'Helping web2py': 'Helping web2py',
'Helping web2py': 'Aider web2py',
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
'Home': 'Accueil',
'How did you get here?': 'How did you get here?',
'import': 'import',
'import': 'importer',
'Import/Export': 'Importer/Exporter',
'Index': 'Index',
'insert new': 'insérer un nouveau',
@@ -104,40 +108,47 @@
'Invalid email': 'Courriel invalide',
'Invalid Query': 'Requête Invalide',
'invalid request': 'requête invalide',
'Key': 'Key',
'Is Active': 'Est actif',
'Key': 'Clé',
'Last name': 'Nom',
'Layout': 'Mise en page',
'Layout Plugins': 'Layout Plugins',
'Layouts': 'layouts',
'Layout Plugins': 'Plugins de mise en page',
'Layouts': 'Mises en page',
'Live chat': 'Clavardage en direct',
'Live Chat': 'Live Chat',
'Log In': 'Log In',
'Live Chat': 'Clavardage en direct',
'Loading...': 'Chargement...',
'loading...': 'chargement...',
'Log In': 'Connexion',
'Logged in': 'Connecté',
'login': 'connectez-vous',
'Login': 'Connectez-vous',
'logout': 'déconnectez-vous',
'login': 'connexion',
'Login': 'Connexion',
'logout': 'déconnexion',
'lost password': 'mot de passe perdu',
'Lost Password': 'Mot de passe perdu',
'Lost password?': 'Mot de passe perdu?',
'lost password?': 'mot de passe perdu?',
'Main Menu': 'Menu principal',
'Manage %(action)s': 'Manage %(action)s',
'Manage Access Control': 'Manage Access Control',
'Manage Cache': 'Manage Cache',
'Manage Cache': 'Gérer le Cache',
'Memberships': 'Memberships',
'Menu Model': 'Menu modèle',
'My Sites': 'My Sites',
'Modified By': 'Modifié par',
'Modified On': 'Modifié le',
'My Sites': 'Mes sites',
'Name': 'Nom',
'New Record': 'Nouvel enregistrement',
'new record inserted': 'nouvel enregistrement inséré',
'next %s rows': 'next %s rows',
'next %s rows': '%s prochaine lignes',
'next 100 rows': '100 prochaines lignes',
'No databases in this application': "Cette application n'a pas de bases de données",
'Number of entries: **%s**': 'Number of entries: **%s**',
'Object or table name': 'Objet ou nom de table',
'Online book': 'Online book',
'Online examples': 'Exemples en ligne',
'or import from csv file': "ou importer d'un fichier CSV",
'Origin': 'Origine',
'Other Plugins': 'Other Plugins',
'Other Plugins': 'Autres Plugiciels',
'Other Recipes': 'Autres recettes',
'Overview': 'Présentation',
'password': 'mot de passe',
@@ -145,33 +156,34 @@
"Password fields don't match": 'Les mots de passe ne correspondent pas',
'Permission': 'Permission',
'Permissions': 'Permissions',
'please input your password again': "S'il vous plaît entrer votre mot de passe",
'please input your password again': "S'il vous plaît entrer votre mot de passe à nouveau",
'Plugins': 'Plugiciels',
'Powered by': 'Alimenté par',
'Preface': 'Préface',
'previous %s rows': 'previous %s rows',
'previous %s rows': '%s lignes précédentes',
'previous 100 rows': '100 lignes précédentes',
'profile': 'profile',
'pygraphviz library not found': 'pygraphviz library not found',
'profile': 'profil',
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
'Python': 'Python',
'Query:': 'Requête:',
'Quick Examples': 'Examples Rapides',
'Quick Examples': 'Exemples Rapides',
'RAM': 'RAM',
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Cleared',
'Ram Cleared': 'Ram vidée',
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Readme': 'Lisez-moi',
'Recipes': 'Recettes',
'Record': 'enregistrement',
'Record %(id)s created': 'Record %(id)s created',
'Record %(id)s updated': 'Record %(id)s updated',
'Record Created': 'Record Created',
'Record %(id)s created': 'Enregistrement %(id)s créé',
'Record %(id)s updated': 'Enregistrement %(id)s modifié',
'Record Created': 'Enregistrement créé',
'record does not exist': "l'archive n'existe pas",
'Record ID': "ID d'enregistrement",
'Record id': "id d'enregistrement",
'Record Updated': 'Record Updated',
'Record ID': "ID de l'enregistrement",
'Record id': "id de l'enregistrement",
'Record Updated': 'Enregistrement modifié',
'Register': "S'inscrire",
'register': "s'inscrire",
'Registration identifier': "Identifiant d'inscription",
'Registration key': "Clé d'enregistrement",
'Registration successful': 'Inscription réussie',
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
@@ -179,18 +191,18 @@
'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources',
'Role': 'Rôle',
'Roles': 'Roles',
'Roles': 'Rôles',
'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées',
'Save model as...': 'Save model as...',
'Save model as...': 'Enregistrer le modèle sous...',
'Semantic': 'Sémantique',
'Services': 'Services',
'Sign Up': 'Sign Up',
'Size of cache:': 'Size of cache:',
'Sign Up': "S'inscrire",
'Size of cache:': 'Taille de la mémoire cache:',
'state': 'état',
'Statistics': 'Statistics',
'Statistics': 'Statistiques',
'Stylesheet': 'Feuille de style',
'submit': 'submit',
'submit': 'soumettre',
'Submit': 'Soumettre',
'Support': 'Soutien',
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
@@ -202,31 +214,31 @@
'The Views': 'Les Vues',
'This App': 'Cette Appli',
'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage",
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
'Time in Cache (h:m:s)': 'Temps en Cache (h:m:s)',
'Timestamp': 'Horodatage',
'Traceback': 'Traceback',
'Twitter': 'Twitter',
'unable to parse csv file': "incapable d'analyser le fichier cvs",
'Update:': 'Mise à jour:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.',
'User': 'User',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT afin de construire des requêtes plus complexes.',
'User': 'Utilisateur',
'User %(id)s Logged-in': 'Utilisateur %(id)s connecté',
'User %(id)s Registered': 'Utilisateur %(id)s enregistré',
'User ID': 'ID utilisateur',
'User Voice': 'User Voice',
'Users': 'Users',
'value already in database or empty': 'valeur déjà dans la base ou vide',
'User Voice': "Voix de l'utilisateur",
'Users': 'Utilisateurs',
'value already in database or empty': 'valeur déjà dans la base ou inexistante',
'Verify Password': 'Vérifiez le mot de passe',
'Videos': 'Vidéos',
'View': 'Présentation',
'View': 'Vue',
'Web2py': 'Web2py',
'Welcome': 'Bienvenu',
'Welcome': 'Bienvenue',
'Welcome %s': 'Bienvenue %s',
'Welcome to web2py': 'Bienvenue à web2py',
'Welcome to web2py!': 'Welcome to web2py!',
'Welcome to web2py!': 'Bienvenue à web2py!',
'Which called the function %s located in the file %s': 'Qui a appelé la fonction %s se trouvant dans le fichier %s',
'Working...': 'Working...',
'You are successfully running web2py': 'Vous roulez avec succès web2py',
'Working...': 'Traitement en cours...',
'You are successfully running web2py': 'Vous exécutez avec succès web2py',
'You can modify this application and adapt it to your needs': "Vous pouvez modifier cette application et l'adapter à vos besoins",
'You visited the url %s': "Vous avez visité l'URL %s",
}
+48 -32
View File
@@ -13,7 +13,9 @@
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'?': '?',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
'about': 'à propos',
'About': 'À propos',
'Access Control': "Contrôle d'accès",
'admin': 'admin',
@@ -31,7 +33,7 @@
'Cache': 'Cache',
'Cache Cleared': 'Cache Cleared',
'Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Cache Keys': 'Clés de cache',
'Cache Keys': 'Cache Keys',
'Cannot be empty': 'Ne peut pas être vide',
'change password': 'changer le mot de passe',
'Check to delete': 'Cliquez pour supprimer',
@@ -41,10 +43,10 @@
'Clear RAM': 'Vider la RAM',
'Client IP': 'IP client',
'Community': 'Communauté',
'Components and Plugins': 'Composants et Plugins',
'Components and Plugins': 'Composants et Plugiciels',
'Config.ini': 'Config.ini',
'Controller': 'Contrôleur',
'Copyright': 'Copyright',
'Copyright': "Droit d'auteur",
'Created By': 'Créé par',
'Created On': 'Créé le',
'Current request': 'Demande actuelle',
@@ -55,8 +57,8 @@
'Database': 'base de données',
'Database %s select': 'base de données %s selectionnée',
'Database Administration (appadmin)': 'Database Administration (appadmin)',
'db': 'bdd',
'DB Model': 'Modèle BDD',
'db': 'db',
'DB Model': 'Modèle BD',
'Delete:': 'Supprimer:',
'Demo': 'Démo',
'Deployment Recipes': 'Recettes de déploiement',
@@ -71,12 +73,13 @@
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
'done!': 'fait!',
'Download': 'Téléchargement',
'E-mail': 'E-mail',
'E-mail': 'Courriel',
'Edit': 'Éditer',
'Edit current record': "Modifier l'enregistrement courant",
'edit profile': 'modifier le profil',
'Edit This App': 'Modifier cette application',
'Email and SMS': 'Email et SMS',
'Email and SMS': 'Courriel et texto',
'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': 'entrez un entier entre %(min)g et %(max)g',
'Errors': 'Erreurs',
'export as csv file': 'exporter sous forme de fichier csv',
@@ -85,22 +88,24 @@
'Forms and Validators': 'Formulaires et Validateurs',
'Free Applications': 'Applications gratuites',
'Function disabled': 'Fonction désactivée',
'Graph Model': 'Graph Model',
'Group ID': 'Groupe ID',
'Graph Model': 'Représentation graphique du modèle',
'Group %(group_id)s created': '%(group_id)s groupe créé',
'Group ID': 'ID du groupe',
'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s",
'Groups': 'Groupes',
'Hello World': 'Bonjour le monde',
'Helping web2py': 'En train daider web2py',
'Helping web2py': 'Aider web2py',
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
'Home': 'Accueil',
'How did you get here?': 'Comment êtes-vous arrivé ici?',
'import': 'Importer',
'How did you get here?': 'How did you get here?',
'import': 'importer',
'Import/Export': 'Importer/Exporter',
'Index': 'Index',
'insert new': 'insérer un nouveau',
'insert new %s': 'insérer un nouveau %s',
'Internal State': 'État interne',
'Introduction': 'Introduction',
'Invalid email': 'E-mail invalide',
'Introduction': 'Présentation',
'Invalid email': 'Courriel invalide',
'Invalid Query': 'Requête Invalide',
'invalid request': 'requête invalide',
'Is Active': 'Est actif',
@@ -109,12 +114,15 @@
'Layout': 'Mise en page',
'Layout Plugins': 'Plugins de mise en page',
'Layouts': 'Mises en page',
'Live chat': 'Chat en direct',
'Live Chat': 'Chat en direct',
'Log In': 'Log In',
'login': 'connectez-vous',
'Login': 'Connectez-vous',
'logout': 'déconnectez-vous',
'Live chat': 'Clavardage en direct',
'Live Chat': 'Clavardage en direct',
'Loading...': 'Chargement...',
'loading...': 'chargement...',
'Log In': 'Connexion',
'Logged in': 'Connecté',
'login': 'connexion',
'Login': 'Connexion',
'logout': 'déconnexion',
'lost password': 'mot de passe perdu',
'Lost Password': 'Mot de passe perdu',
'Lost password?': 'Mot de passe perdu?',
@@ -131,7 +139,7 @@
'Name': 'Nom',
'New Record': 'Nouvel enregistrement',
'new record inserted': 'nouvel enregistrement inséré',
'next %s rows': 'next %s rows',
'next %s rows': '%s prochaine lignes',
'next 100 rows': '100 prochaines lignes',
'No databases in this application': "Cette application n'a pas de bases de données",
'Number of entries: **%s**': 'Number of entries: **%s**',
@@ -140,55 +148,63 @@
'Online examples': 'Exemples en ligne',
'or import from csv file': "ou importer d'un fichier CSV",
'Origin': 'Origine',
'Other Plugins': 'Autres Plugins',
'Other Plugins': 'Autres Plugiciels',
'Other Recipes': 'Autres recettes',
'Overview': 'Présentation',
'password': 'mot de passe',
'Password': 'Mot de passe',
"Password fields don't match": 'Les mots de passe ne correspondent pas',
'Permission': 'Permission',
'Permissions': 'Permissions',
'Plugins': 'Plugins',
'please input your password again': "S'il vous plaît entrer votre mot de passe à nouveau",
'Plugins': 'Plugiciels',
'Powered by': 'Alimenté par',
'Preface': 'Préface',
'previous %s rows': 'previous %s rows',
'previous %s rows': '%s lignes précédentes',
'previous 100 rows': '100 lignes précédentes',
'profile': 'profil',
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
'Python': 'Python',
'Query:': 'Requête:',
'Quick Examples': 'Exemples Rapides',
'RAM': 'RAM',
'RAM Cache Keys': 'Clés de cache de la RAM',
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram vidée',
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
'Readme': 'Lisez-moi',
'Recipes': 'Recettes',
'Record': 'enregistrement',
'Record %(id)s created': 'Enregistrement %(id)s créé',
'Record %(id)s updated': 'Enregistrement %(id)s modifié',
'Record Created': 'Enregistrement créé',
'record does not exist': "l'archive n'existe pas",
'Record ID': "ID d'enregistrement",
'Record id': "id d'enregistrement",
'Record ID': "ID de l'enregistrement",
'Record id': "id de l'enregistrement",
'Record Updated': 'Enregistrement modifié',
'Register': "S'inscrire",
'register': "s'inscrire",
'Registration identifier': "Identifiant d'enregistrement",
'Registration identifier': "Identifiant d'inscription",
'Registration key': "Clé d'enregistrement",
'Registration successful': 'Inscription réussie',
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
'Request reset password': 'Demande de réinitialiser le mot clé',
'Reset Password key': 'Réinitialiser le mot clé',
'Resources': 'Ressources',
'Role': 'Rôle',
'Roles': 'Roles',
'Roles': 'Rôles',
'Rows in Table': 'Lignes du tableau',
'Rows selected': 'Lignes sélectionnées',
'Save model as...': 'Enregistrer le modèle sous...',
'Semantic': 'Sémantique',
'Services': 'Services',
'Sign Up': 'Sign Up',
'Size of cache:': 'Taille du cache:',
'Sign Up': "S'inscrire",
'Size of cache:': 'Taille de la mémoire cache:',
'state': 'état',
'Statistics': 'Statistiques',
'Stylesheet': 'Feuille de style',
'submit': 'soumettre',
'Submit': 'Soumettre',
'Support': 'Support',
'Support': 'Soutien',
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Table': 'tableau',
'Table name': 'Nom du tableau',
+2 -2
View File
@@ -20,8 +20,8 @@
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
# ----------------------------------------------------------------------------------------------------------------------
from fileutils import abspath
from languages import read_possible_languages
from gluon.fileutils import abspath
from gluon.languages import read_possible_languages
possible_languages = read_possible_languages(abspath('applications', app))
# ----------------------------------------------------------------------------------------------------------------------
File diff suppressed because one or more lines are too long
+71 -19
View File
@@ -12,6 +12,8 @@
$.error('web2py.js has already been loaded!');
}
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -38,8 +40,12 @@
if (value > 0) $('#' + id).hide().fadeIn('slow');
else $('#' + id).show().fadeOut('slow');
},
ajax: function (u, s, t) {
ajax: function (u, s, t, options) {
/*simple ajax function*/
// set options default value
options = typeof options !== 'undefined' ? options : {};
var query = '';
if (typeof s == 'string') {
var d = $(s).serialize();
@@ -59,18 +65,44 @@
query = pcs.join('&');
}
}
$.ajax({
// default success action
var success_function = function (msg) {
if (t) {
if (t == ':eval') eval(msg);
else if (typeof t == 'string') $('#' + t).html(msg);
else t(msg);
}
};
// declare success actions as array
var success = [success_function];
// add user success actions
if ($.isArray(options.done)){
success = $.merge(success, options.done);
} else {
success.push(options.done);
}
// default jquery ajax options
var ajax_options = {
type: 'POST',
url: u,
data: query,
success: function (msg) {
if (t) {
if (t == ':eval') eval(msg);
else if (typeof t == 'string') $('#' + t).html(msg);
else t(msg);
}
}
});
success: success
};
//remove custom "done" option if exists
delete options.done;
// merge default ajax options with user custom options
for (var attrname in options) {
ajax_options[attrname] = options[attrname];
}
// call ajax function
$.ajax(ajax_options);
},
ajax_fields: function (target) {
/*
@@ -168,7 +200,8 @@
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.w2p_flash', function () {
doc.on('click', '.w2p_flash', function (event) {
event.preventDefault();
var t = $(this);
if (t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
@@ -289,7 +322,15 @@
form.submit(function (e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash();
web2py.ajax_page('post', url, form.serialize(), target, form);
var formData;
if (FORMDATA_IS_SUPPORTED) {
formData = new FormData(form[0]); // Allows file uploads.
} else {
formData = form.serialize(); // Fallback for older browsers.
}
web2py.ajax_page('post', url, formData, target, form);
e.preventDefault();
});
form.on('click', web2py.formInputClickSelector, function (e) {
@@ -308,14 +349,22 @@
if (web2py.isUndefined(element)) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
/* processData and contentType must be set to false when passing a FormData
object to jQuery.ajax. */
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
$.ajax({
'type': method,
'url': action,
'data': data,
'processData': !isFormData,
'contentType': contentType,
'beforeSend': function (xhr, settings) {
xhr.setRequestHeader('web2py-component-location', document.location);
xhr.setRequestHeader('web2py-component-element', target);
web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
},
'success': function (data, status, xhr) {
@@ -667,8 +716,9 @@
});
},
/* Disables form elements:
- Does not disable elements with 'data-w2p_disable' attribute
- Caches element value in 'w2p_enable_with' data store
- Replaces element text with value of 'data-disable-with' attribute
- Replaces element text with value of 'data-w2p_disable_with' attribute
- Sets disabled property to true
*/
disableFormElements: function (form) {
@@ -680,13 +730,15 @@
if (!web2py.isUndefined(disable)) {
return false;
}
if (web2py.isUndefined(disable_with)) {
element.data('w2p_disable_with', element[method]());
if (!element.is(':file')) { // Altering file input values is not allowed.
if (web2py.isUndefined(disable_with)) {
element.data('w2p_disable_with', element[method]());
}
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
}
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
element.data('w2p_enable_with', element[method]());
}
element[method](element.data('w2p_disable_with'));
element.prop('disabled', true);
});
},
+13 -21
View File
@@ -233,30 +233,22 @@
<div class="clear"></div>
{{pass}}
{{if request.function=='graph_model':}}
{{if request.function=='d3_graph_model':}}
<h2>{{=T("Graph Model")}}</h2>
{{if not pgv:}}
{{=T('pygraphviz library not found')}}
{{elif not databases:}}
{{if not databases:}}
{{=T("No databases in this application")}}
{{else:}}
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-download"></i> {{=T('Save model as...')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul>
</div>
<br />
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
{{else:}}
<div id="vis"></div>
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
<script>
// Define the d3 input data
{{from gluon.serializers import json }}
var nodes = {{=XML(json(nodes))}};
var links = {{=XML(json(links))}};
d3_graph();
</script>
{{pass}}
{{pass}}
{{pass}}
{{if request.function == 'manage':}}
<h2>{{=heading}}</h2>
+1 -1
View File
@@ -7,7 +7,7 @@ It is used as default when a view is not provided for your controllers
"""}}
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
{{if len(response._vars)==1:}}
{{=BEAUTIFY(response._vars.values()[0])}}
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
{{elif len(response._vars)>1:}}
{{=BEAUTIFY(response._vars)}}
{{pass}}
+2 -2
View File
@@ -7,7 +7,7 @@
# it is not safe to use as a generic.jsonp because of security implications.
if response.view == 'generic.jsonp':
raise HTTP(501,'generic.jsonp diasbled for security reasons')
raise HTTP(501,'generic.jsonp disabled for security reasons')
try:
from gluon.serializers import json
@@ -20,4 +20,4 @@ except ImportError:
raise HTTP(405, 'JSON not available')
except:
raise HTTP(405, 'JSON error')
}}
}}
+1 -1
View File
@@ -27,4 +27,4 @@ Notice:
- no need to return a string
even if the function is called via ajax.
'''}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
'''}}{{if len(response._vars)==1:}}{{=response._vars[next(iter(response._vars))]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
+1 -1
View File
@@ -59,7 +59,7 @@
</button>
{{=response.logo or ''}}
</div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}}
</ul>
Vendored
+3 -2
View File
@@ -1,7 +1,8 @@
from fabric.api import *
from fabric.operations import put, get
from fabric.contrib.files import exists
from fabric.contrib.files import exists, append, uncomment
import os
import crypt
import datetime
import getpass
@@ -19,11 +20,11 @@ def create_user(username):
"""fab -H root@host create_user:username"""
password = getpass.getpass('password for %s> ' % username)
run('useradd -m -G www-data -s /bin/bash -p %s %s' % (crypt.crypt(password, 'salt'), username))
local('ssh-copy-id %s' % env.hosts[0])
run('cp /etc/sudoers /tmp/sudoers.new')
append('/tmp/sudoers.new', '%s ALL=(ALL) NOPASSWD:ALL' % username, use_sudo=True)
run('visudo -c -f /tmp/sudoers.new')
run('EDITOR="cp /tmp/sudoers.new" visudo')
local('ssh-copy-id %s' % env.hosts[0])
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
def install_web2py():
+2
View File
@@ -32,6 +32,7 @@ if PY2:
import cgi
import cookielib
from xmlrpclib import ProtocolError
from gluon.contrib import ipaddress
BytesIO = StringIO
reduce = reduce
hashlib_md5 = hashlib.md5
@@ -97,6 +98,7 @@ else:
from http import cookiejar as cookielib
from xmlrpc.client import ProtocolError
import html # warning, this is the python3 module and not the web2py html module
import ipaddress
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
+20 -11
View File
@@ -54,7 +54,8 @@ def app_pack(app, request, raise_ex=False, filenames=None):
"""
try:
if filenames is None: app_cleanup(app, request)
if filenames is None:
app_cleanup(app, request)
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
w2p_pack(filename, apath(app, request), filenames=filenames)
return filename
@@ -104,7 +105,8 @@ def app_cleanup(app, request):
if os.path.exists(path):
for f in os.listdir(path):
try:
if f[:1] != '.': os.unlink(os.path.join(path, f))
if f[:1] != '.':
os.unlink(os.path.join(path, f))
except IOError:
r = False
@@ -113,7 +115,8 @@ def app_cleanup(app, request):
if os.path.exists(path):
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
if f[:1] != '.':
recursive_unlink(os.path.join(path, f))
except (OSError, IOError):
r = False
@@ -123,7 +126,8 @@ def app_cleanup(app, request):
CacheOnDisk(folder=path).clear()
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
if f[:1] != '.':
recursive_unlink(os.path.join(path, f))
except (OSError, IOError):
r = False
return r
@@ -175,10 +179,9 @@ def app_create(app, request, force=False, key=None, info=False):
return False
try:
w2p_unpack('welcome.w2p', path)
for subfolder in [
'models', 'views', 'controllers', 'databases',
'modules', 'cron', 'errors', 'sessions', 'cache',
'languages', 'static', 'private', 'uploads']:
for subfolder in ['models', 'views', 'controllers', 'databases',
'modules', 'cron', 'errors', 'sessions', 'cache',
'languages', 'static', 'private', 'uploads']:
subpath = os.path.join(path, subfolder)
if not os.path.exists(subpath):
os.mkdir(subpath)
@@ -368,7 +371,7 @@ def unzip(filename, dir, subfolder=''):
for name in sorted(zf.namelist()):
if not name.startswith(subfolder):
continue
#print name[n:]
# print name[n:]
if name.endswith('/'):
folder = os.path.join(dir, name[n:])
if not os.path.exists(folder):
@@ -435,16 +438,22 @@ def add_path_first(path):
if not global_settings.web2py_runtime_gae:
site.addsitedir(path)
def try_mkdir(path):
if not os.path.exists(path):
try:
os.mkdir(path)
if os.path.islink(path):
# path is a broken link, try to mkdir the target of the link instead of the link itself.
os.mkdir(os.path.realpath(path))
else:
os.mkdir(path)
except OSError as e:
if e.strerror == 'File exists': # In case of race condition.
if e.strerror == 'File exists': # In case of race condition.
pass
else:
raise e
def create_missing_folders():
if not global_settings.web2py_runtime_gae:
for path in ('applications', 'deposit', 'site-packages', 'logs'):
+1038
View File
File diff suppressed because it is too large Load Diff
+44 -31
View File
@@ -41,11 +41,12 @@ import imp
import logging
import types
from functools import reduce
logger = logging.getLogger("web2py")
from gluon import rewrite
from gluon.custom_import import custom_import_install
import py_compile
logger = logging.getLogger("web2py")
is_pypy = settings.global_settings.is_pypy
is_gae = settings.global_settings.web2py_runtime_gae
is_jython = settings.global_settings.is_jython
@@ -111,7 +112,7 @@ class mybuiltin(object):
NOTE could simple use a dict and populate it,
NOTE not sure if this changes things though if monkey patching import.....
"""
#__builtins__
# __builtins__
def __getitem__(self, key):
try:
return getattr(builtin, key)
@@ -185,7 +186,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
else:
statement = "$.web2py.component('%s','%s');" % (url, target)
attr['_data-w2p_remote'] = url
if not target is None:
if target is not None:
return DIV(content, **attr)
else:
@@ -211,7 +212,8 @@ def LOAD(c=None, f='index', args=None, vars=None,
request.env.path_info
other_request.cid = target
other_request.env.http_web2py_component_element = target
other_request.restful = types.MethodType(request.restful.__func__, other_request) # A bit nasty but needed to use LOAD on action decorates with @request.restful()
other_request.restful = types.MethodType(request.restful.__func__, other_request)
# A bit nasty but needed to use LOAD on action decorates with @request.restful()
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
other_environment = copy.copy(current.globalenv) # NASTY
@@ -405,7 +407,7 @@ def build_environment(request, response, session, store_current=True):
"""
Build the environment dictionary into which web2py files are executed.
"""
#h,v = html,validators
# h,v = html,validators
environment = dict(_base_environment_)
if not request.env:
@@ -418,7 +420,7 @@ def build_environment(request, response, session, store_current=True):
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
]
t = environment['T'] = translator(os.path.join(request.folder,'languages'),
t = environment['T'] = translator(os.path.join(request.folder, 'languages'),
request.env.http_accept_language)
c = environment['cache'] = Cache(request)
@@ -506,10 +508,12 @@ def compile_models(folder):
save_pyc(filename)
os.unlink(filename)
def find_exposed_functions(data):
data = regex_longcomments.sub('',data)
data = regex_longcomments.sub('', data)
return regex_expose.findall(data)
def compile_controllers(folder):
"""
Compiles all the controllers in the application specified by `folder`
@@ -524,16 +528,19 @@ def compile_controllers(folder):
command = data + "\nresponse._vars=response._caller(%s)\n" % \
function
filename = pjoin(folder, 'compiled',
'controllers.%s.%s.py' % (fname[:-3],function))
'controllers.%s.%s.py' % (fname[:-3], function))
write_file(filename, command)
save_pyc(filename)
os.unlink(filename)
def model_cmp(a, b, sep='.'):
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
def model_cmp_sep(a, b, sep=os.path.sep):
return model_cmp(a,b,sep)
return model_cmp(a, b, sep)
def run_models_in(environment):
"""
@@ -544,7 +551,7 @@ def run_models_in(environment):
request = current.request
folder = request.folder
c = request.controller
#f = environment['request'].function
# f = environment['request'].function
response = current.response
path = pjoin(folder, 'models')
@@ -557,9 +564,11 @@ def run_models_in(environment):
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
else:
if compiled:
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), key=lambda f: '{0:03d}'.format(f.count('.')) + f)
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0),
key=lambda f: '{0:03d}'.format(f.count('.')) + f)
else:
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False),
key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
models_to_run = None
for model in models:
@@ -570,10 +579,10 @@ def run_models_in(environment):
if models_to_run:
if compiled:
n = len(cpath)+8
fname = model[n:-4].replace('.','/')+'.py'
fname = model[n:-4].replace('.', '/')+'.py'
else:
n = len(path)+1
fname = model[n:].replace(os.path.sep,'/')
fname = model[n:].replace(os.path.sep, '/')
if not regex.search(fname) and c != 'appadmin':
continue
elif compiled:
@@ -583,6 +592,7 @@ def run_models_in(environment):
ccode = getcfs(model, model, f)
restricted(ccode, environment, layer=model)
def run_controller_in(controller, function, environment):
"""
Runs the controller.function() (for the app specified by
@@ -596,13 +606,13 @@ def run_controller_in(controller, function, environment):
badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function)
if os.path.exists(cpath):
filename = pjoin(cpath, 'controllers.%s.%s.pyc'
% (controller, function))
if not os.path.exists(filename):
filename = pjoin(cpath, 'controllers.%s.%s.pyc' % (controller, function))
try:
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
except IOError:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
elif function == '_TEST':
# TESTING: adjust the path to include site packages
from gluon.settings import global_settings
@@ -623,24 +633,24 @@ def run_controller_in(controller, function, environment):
code += TEST_CODE
ccode = compile2(code, filename)
else:
filename = pjoin(folder, 'controllers/%s.py'
% controller)
if not os.path.exists(filename):
filename = pjoin(folder, 'controllers/%s.py' % controller)
try:
code = getcfs(filename, filename, lambda: read_file(filename))
except IOError:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badc,
web2py_error=badc)
code = getcfs(filename, filename, lambda: read_file(filename))
exposed = find_exposed_functions(code)
if not function in exposed:
if function not in exposed:
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badf,
web2py_error=badf)
code = "%s\nresponse._vars=response._caller(%s)" % (code, function)
layer = "%s:%s" % (filename, function)
ccode = getcfs(layer, filename, lambda: compile2(code, layer))
ccode = getcfs(layer, filename, lambda: compile2(code, filename))
restricted(ccode, environment, layer=filename)
response = current.response
response = environment["response"]
vars = response._vars
if response.postprocessing:
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
@@ -678,7 +688,7 @@ def run_view_in(environment):
layer = 'file stream'
else:
filename = pjoin(folder, 'views', view)
if os.path.exists(cpath): # compiled views
if os.path.exists(cpath): # compiled views
x = view.replace('/', '.')
files = ['views.%s.pyc' % x]
is_compiled = os.path.exists(pjoin(cpath, files[0]))
@@ -705,16 +715,19 @@ def run_view_in(environment):
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
layer = filename
# Compile the template
ccode = parse_template(view,
pjoin(folder, 'views'),
context=environment)
# if the view is not compiled
if not layer:
# Compile the template
ccode = parse_template(view,
pjoin(folder, 'views'),
context=environment)
layer = filename
restricted(ccode, environment, layer=layer)
# parse_template saves everything in response body
return environment['response'].body.getvalue()
def remove_compiled_application(folder):
"""
Deletes the folder `compiled` containing the compiled application.
+3 -3
View File
@@ -330,7 +330,7 @@ CONTENT_TYPE = {
'.lha': 'application/x-lha',
'.lhs': 'text/x-literate-haskell',
'.lhz': 'application/x-lhz',
'.load' : 'text/html',
'.load': 'text/html',
'.log': 'text/x-log',
'.lrz': 'application/x-lrzip',
'.ltx': 'text/x-tex',
@@ -823,7 +823,7 @@ CONTENT_TYPE = {
'.xsd': 'application/xml',
'.xsl': 'application/xslt+xml',
'.xslfo': 'text/x-xslfo',
'.xslm' : 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.xslm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
'.xslt': 'application/xslt+xml',
'.xspf': 'application/xspf+xml',
'.xul': 'application/vnd.mozilla.xul+xml',
@@ -843,7 +843,7 @@ def contenttype(filename, default='text/plain'):
"""
Returns the Content-Type string matching extension of the given filename.
"""
filename=to_native(filename)
filename = to_native(filename)
i = filename.rfind('.')
if i >= 0:
default = CONTENT_TYPE.get(filename[i:].lower(), default)
+4 -6
View File
@@ -7,15 +7,13 @@ db = get_db()
"""
import os
from gluon import *
from pydal.adapters import ADAPTERS, PostgreSQLAdapter
from pydal.helpers.classes import UseDatabaseStoredFile
from pydal.adapters import adapters, PostgrePsyco
from pydal.helpers.classes import DatabaseStoredFile
class HerokuPostgresAdapter(UseDatabaseStoredFile,PostgreSQLAdapter):
drivers = ('psycopg2',)
@adapters.register_for('postgres')
class HerokuPostgresAdapter(DatabaseStoredFile, PostgrePsyco):
uploads_in_blob = True
ADAPTERS['postgres'] = HerokuPostgresAdapter
def get_db(name = None, pool_size=10):
if not name:
names = [n for n in os.environ.keys()
+1 -1
View File
@@ -45,7 +45,7 @@ class RESIZE(object):
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
background.paste(
img,
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
((self.nx - img.size[0]) // 2, (self.ny - img.size[1]) // 2))
background.save(s, 'JPEG', quality=self.quality)
else:
img.save(s, 'JPEG', quality=self.quality)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -506,9 +506,9 @@ def ldap_auth(server='ldap',
l = []
for group in ldap_groups_of_the_user:
if group in group_mapping:
l += group_mapping[group]
l.append(group_mapping[group])
ldap_groups_of_the_user = l
logging.info("User groups after remapping: %s" % str(l))
logger.info("User groups after remapping: %s" % str(l))
#
# Get all group name where the user is in actually in local db
@@ -528,7 +528,7 @@ def ldap_auth(server='ldap',
except AttributeError as e:
db_user_id = db.auth_user.insert(email=username, first_name=username)
if not db_user_id:
logging.error(
logger.error(
'There is no username or email for %s!' % username)
raise
# if old pydal version, assume this is a relational database which can do joins
@@ -550,7 +550,7 @@ def ldap_auth(server='ldap',
for group in db_group_search.select(db.auth_group.id, db.auth_group.role, distinct=True):
db_group_id[group.role] = group.id
db_groups_of_the_user.append(group.role)
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
logger.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
auth_membership_changed = False
#
+57 -26
View File
@@ -5,12 +5,19 @@
# license MIT/BSD/GPL
from __future__ import print_function
import re
import sys
import urllib
from gluon._compat import maketrans, urllib_quote, unicodeT, to_bytes, to_native, xrange
from gluon.utils import local_html_escape as escape
from ast import parse as ast_parse
import ast
PY2 = sys.version_info[0] == 2
if PY2:
from urllib import quote as urllib_quote
from string import maketrans
else:
from urllib.parse import quote as urllib_quote
maketrans = str.maketrans
"""
TODO: next version should use MathJax
@@ -544,7 +551,7 @@ 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_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*)?(.*)$')
@@ -564,6 +571,29 @@ ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def local_html_escape(data, quote=False):
"""
Works with bytes.
Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true (the default), the quotation mark
characters, both double quote (") and single quote (') characters are also
translated.
"""
if PY2:
import cgi
data = cgi.escape(data, quote)
return data.replace("'", "&#x27;") if quote else data
else:
import html
if isinstance(data, str):
return html.escape(data, quote=quote)
data = data.replace(b"&", b"&amp;") # Must be done first!
data = data.replace(b"<", b"&lt;")
data = data.replace(b">", b"&gt;")
if quote:
data = data.replace(b'"', b"&quot;")
data = data.replace(b'\'', b"&#x27;")
return data
def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':", b)
@@ -579,7 +609,7 @@ def safe_eval(node_or_string, env):
_safe_names = {'None': None, 'True': True, 'False': False}
_safe_names.update(env)
if isinstance(node_or_string, basestring):
node_or_string = ast_parse(node_or_string, mode='eval')
node_or_string = ast.parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
@@ -599,14 +629,14 @@ def safe_eval(node_or_string, env):
if node.id in _safe_names:
return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.op, (ast.Add, ast.Sub)) and \
isinstance(node.right, ast.Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left, ast.Num) and \
isinstance(node.left.n, (int, long, float)):
left = node.left.n
right = node.right.n
if isinstance(node.op, Add):
if isinstance(node.op, ast.Add):
return left + right
else:
return left - right
@@ -765,7 +795,7 @@ def render(text,
'<table><tbody><tr class="first"><td>a</td><td>b</td></tr><tr class="even"><td>c</td><td>d</td></tr></tbody></table>'
>>> render("----\\nhello world\\n----\\n")
'<blockquote>hello world</blockquote>'
'<blockquote><p>hello world</p></blockquote>'
>>> render('[[myanchor]]')
'<p><span class="anchor" id="markmin_myanchor"></span></p>'
@@ -946,7 +976,8 @@ def render(text,
if protolinks == "default":
protolinks = protolinks_simple
pp = '\n' if pretty_print else ''
text = to_native(text)
text = text if text is None or isinstance(text, str) else text.decode('utf8', 'strict')
if not (isinstance(text, str)):
text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
@@ -994,7 +1025,7 @@ def render(text,
return LINK
text = regex_link.sub(mark_link, text)
text = escape(text)
text = local_html_escape(text)
if protolinks:
text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text)
@@ -1035,7 +1066,7 @@ def render(text,
if pend and mtag == '.': # paragraph in a list:
out.append(etags.pop())
ltags.pop()
for i in xrange(lent - lev):
for i in range(lent - lev):
out.append('<' + tag + '>' + pp)
etags.append('</' + tag + '>' + pp)
lev += 1
@@ -1044,7 +1075,7 @@ def render(text,
elif lent == lev:
if tlev[-1] != tag:
# type of list is changed (ul<=>ol):
for i in xrange(ltags.count(lent)):
for i in range(ltags.count(lent)):
ltags.pop()
out.append(etags.pop())
tlev[-1] = tag
@@ -1209,7 +1240,7 @@ def render(text,
s = '<blockquote%s%s>%s</blockquote>%s' \
% (t_cls,
t_id,
'\n'.join(strings[bq_begin:lineno]), pp)
render('\n'.join(strings[bq_begin:lineno])), pp)
mtag = 'q'
else:
s = '<hr />'
@@ -1322,10 +1353,10 @@ def render(text,
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
if not k:
return m.group(0)
k = escape(k)
k = local_html_escape(k)
t = t or ''
style = 'width:%s' % w if w else ''
title = ' title="%s"' % escape(a).replace(META, DISABLED_META) if a else ''
title = ' title="%s"' % local_html_escape(a).replace(META, DISABLED_META) if a else ''
p_begin = p_end = ''
if p == 'center':
p_begin = '<p style="text-align:center">'
@@ -1349,7 +1380,7 @@ def render(text,
autolinks, protolinks, class_prefix, id_prefix, pretty_print)
return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
% dict(p=p, title=title, style=style, k=k, t=t)
alt = ' alt="%s"' % escape(t).replace(META, DISABLED_META) if t else ''
alt = ' alt="%s"' % local_html_escape(t).replace(META, DISABLED_META) if t else ''
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
@@ -1358,12 +1389,12 @@ def render(text,
if not k and not t:
return m.group(0)
t = t or ''
a = escape(a) if a else ''
a = local_html_escape(a) if a else ''
if k:
if '#' in k and ':' not in k.split('#')[0]:
# wikipage, not external url
k = k.replace('#', '#' + id_prefix)
k = escape(k)
k = local_html_escape(k)
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
target = ' target="_blank"' if p == 'popup' else ''
t = render(t, {}, {}, 'br', URL, environment, latex, None,
@@ -1373,7 +1404,7 @@ def render(text,
if t == 'NEWLINE' and not a:
return '<br />' + pp
return '<span class="anchor" id="%s">%s</span>' % (
escape(id_prefix + t),
local_html_escape(id_prefix + t),
render(a, {}, {}, 'br', URL,
environment, latex, autolinks,
protolinks, class_prefix,
@@ -1399,7 +1430,7 @@ def render(text,
def expand_meta(m):
code, b, p, s = segments.pop(0)
if code is None or m.group() == DISABLED_META:
return escape(s)
return local_html_escape(s)
if b in extra:
if code[:1] == '\n':
code = code[1:]
@@ -1411,7 +1442,7 @@ def render(text,
return str(extra[b](code))
elif b == 'cite':
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
(id_prefix + d, b, d) for d in local_html_escape(code).split(',')) + ']'
elif b == 'latex':
return LATEX % urllib_quote(code)
elif b in html_colors:
@@ -1426,12 +1457,12 @@ def render(text,
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
id = ' id="%s%s"' % (id_prefix, escape(p)) if p else ''
id = ' id="%s%s"' % (id_prefix, local_html_escape(p)) if p else ''
beg = (code[:1] == '\n')
end = [None, -1][code[-1:] == '\n']
if beg and end:
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, local_html_escape(code[1:-1]), pp)
return '<code%s%s>%s</code>' % (cls, id, local_html_escape(code[beg:end]))
text = regex_expand_meta.sub(expand_meta, text)
+3
View File
@@ -21,6 +21,9 @@ def MemcacheClient(*a, **b):
class MemcacheClientObj(Client):
def initialize(self):
pass
meta_storage = {}
max_time_expire = 24*3600
+4 -1
View File
@@ -10,8 +10,11 @@ Original author: Zachary Voase
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
"""
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from StringIO import StringIO # The pure-Python StringIO supports unicode.
import re
+18 -8
View File
@@ -8,30 +8,40 @@ Created by: Ross Peoples <ross.peoples@gmail.com>
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
"""
import cssmin
import jsmin
from . import cssmin
from . import jsmin
import os
import hashlib
import re
import sys
PY2 = sys.version_info[0] == 2
if PY2:
hashlib_md5 = hashlib.md5
else:
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
def open_py23(filename, mode):
if PY2:
f = open(filename, mode + 'b')
else:
f = open(filename, mode, encoding="utf8")
return f
def read_binary_file(filename):
f = open(filename, 'rb')
f = open_py23(filename, 'r')
data = f.read()
f.close()
return data
def write_binary_file(filename, data):
f = open(filename, 'wb')
f = open_py23(filename, 'w')
f.write(data)
f.close()
def fix_links(css, static_path):
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
def minify(files, path_info, folder, optimize_css, optimize_js,
ignore_concat=[],
ignore_minify=['/jquery.js', '/anytime.js']):
@@ -109,7 +119,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
js.append(contents)
else:
js.append(filename)
dest_key = hashlib.md5(repr(processed)).hexdigest()
dest_key = hashlib_md5(repr(processed)).hexdigest()
if css and concat_css:
css = '\n\n'.join(contents for contents in css)
if not inline_css:
+3 -3
View File
@@ -105,14 +105,14 @@ class ServerProxy(object):
def __getattr__(self, attr):
"pseudo method that can be called"
return lambda *args: self.call(attr, *args)
return lambda *args, **vars: self.call(attr, *args, **vars)
def call(self, method, *args):
def call(self, method, *args, **vars):
"JSON RPC communication (method invocation)"
# build data sent to the service
request_id = random.randint(0, sys.maxsize)
data = {'id': request_id, 'method': method, 'params': args, }
data = {'id': request_id, 'method': method, 'params': args or vars, }
if self.version:
data['jsonrpc'] = self.version #mandatory key/value for jsonrpc2 validation else err -32600
request = json.dumps(data)
+2 -2
View File
@@ -43,6 +43,7 @@ class WebClient(object):
self.forms = {}
self.history = []
self.cookies = {}
self.cookiejar = cookielib.CookieJar()
self.default_headers = default_headers
self.sessions = {}
self.session_regex = session_regex and re.compile(session_regex)
@@ -79,9 +80,8 @@ class WebClient(object):
cookies = cookies or {}
headers = headers or {}
cj = cookielib.CookieJar()
args = [
urllib2.HTTPCookieProcessor(cj),
urllib2.HTTPCookieProcessor(self.cookiejar),
urllib2.HTTPHandler(debuglevel=0)
]
# if required do basic auth
+2 -2
View File
@@ -75,7 +75,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
try:
oname = name if not name.startswith('.') else '.'+name
return NATIVE_IMPORTER(oname, globals, locals, fromlist, level)
except ImportError:
except (ImportError, KeyError):
items = current.request.folder.split(os.path.sep)
if not items[-1]:
items = items[:-1]
@@ -100,7 +100,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
import_tb = sys.exc_info()[2]
try:
return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
except ImportError as e3:
except (ImportError, KeyError) as e3:
raise ImportError(e1, import_tb) # there an import error in the module
except Exception as e2:
raise # there is an error in the module
+8 -8
View File
@@ -14,6 +14,11 @@ from pydal import DAL as DAL
from pydal import Field
from pydal.objects import Row, Rows, Table, Query, Set, Expression
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
from gluon.serializers import custom_json, xml
from gluon.utils import web2py_uuid
from gluon import sqlhtml
from pydal.drivers import DRIVERS
def _default_validators(db, field):
"""
@@ -78,14 +83,10 @@ def _default_validators(db, field):
if (field.notnull or field.unique) and field_type not in excluded_fields:
requires.insert(0, validators.IS_NOT_EMPTY())
elif not field.notnull and not field.unique and requires:
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
requires[0] = \
validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
return requires
from gluon.serializers import custom_json, xml
from gluon.utils import web2py_uuid
from gluon import sqlhtml
DAL.serializers = {'json': custom_json, 'xml': xml}
DAL.validators_method = _default_validators
DAL.uuid = lambda x: web2py_uuid()
@@ -96,8 +97,7 @@ DAL.representers = {
DAL.Field = Field
DAL.Table = Table
#: add web2py contrib drivers to pyDAL
from pydal.drivers import DRIVERS
# add web2py contrib drivers to pyDAL
if not DRIVERS.get('pymysql'):
try:
from .contrib import pymysql
+13 -13
View File
@@ -89,7 +89,7 @@ def communicate(command=None):
# New debugger implementation using dbg and a web UI
import gluon.contrib.dbg as dbg
import gluon.contrib.dbg as c_dbg
from threading import RLock
interact_lock = RLock()
@@ -109,11 +109,11 @@ def check_interaction(fn):
return check_fn
class WebDebugger(dbg.Frontend):
class WebDebugger(c_dbg.Frontend):
"""Qdb web2py interface"""
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None):
dbg.Frontend.__init__(self, pipe)
c_dbg.Frontend.__init__(self, pipe)
self.clear_interaction()
def clear_interaction(self):
@@ -128,7 +128,7 @@ class WebDebugger(dbg.Frontend):
run_lock.acquire()
try:
while self.pipe.poll():
dbg.Frontend.run(self)
c_dbg.Frontend.run(self)
finally:
run_lock.release()
@@ -149,23 +149,23 @@ class WebDebugger(dbg.Frontend):
@check_interaction
def do_continue(self):
dbg.Frontend.do_continue(self)
c_dbg.Frontend.do_continue(self)
@check_interaction
def do_step(self):
dbg.Frontend.do_step(self)
c_dbg.Frontend.do_step(self)
@check_interaction
def do_return(self):
dbg.Frontend.do_return(self)
c_dbg.Frontend.do_return(self)
@check_interaction
def do_next(self):
dbg.Frontend.do_next(self)
c_dbg.Frontend.do_next(self)
@check_interaction
def do_quit(self):
dbg.Frontend.do_quit(self)
c_dbg.Frontend.do_quit(self)
def do_exec(self, statement):
interact_lock.acquire()
@@ -175,18 +175,18 @@ class WebDebugger(dbg.Frontend):
# avoid spurious interaction notifications:
self.set_burst(2)
# execute the statement in the remote debugger:
return dbg.Frontend.do_exec(self, statement)
return c_dbg.Frontend.do_exec(self, statement)
finally:
interact_lock.release()
# create the connection between threads:
parent_queue, child_queue = Queue.Queue(), Queue.Queue()
front_conn = dbg.QueuePipe("parent", parent_queue, child_queue)
child_conn = dbg.QueuePipe("child", child_queue, parent_queue)
front_conn = c_dbg.QueuePipe("parent", parent_queue, child_queue)
child_conn = c_dbg.QueuePipe("child", child_queue, parent_queue)
web_debugger = WebDebugger(front_conn) # frontend
dbg_debugger = dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
dbg_debugger = c_dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
dbg = dbg_debugger
# enable getting context (stack, globals/locals) at interaction
+14 -15
View File
@@ -9,19 +9,19 @@ Based on http://code.activestate.com/recipes/52257/
Licensed under the PSF License
"""
from gluon._compat import to_unicode
import codecs
# None represents a potentially variable byte. "##" in the XML spec...
autodetect_dict = { # bytepattern : ("name",
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
(0xFE, 0xFF, None, None): ("utf_16_be"),
(0xFF, 0xFE, None, None): ("utf_16_le"),
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
(0xFE, 0xFF, None, None): ("utf_16_be"),
(0xFF, 0xFE, None, None): ("utf_16_le"),
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
}
@@ -36,10 +36,10 @@ def autoDetectXMLEncoding(buffer):
# buffer at once but otherwise we'd have to decode a character at
# a time looking for the quote character...that's a pain
encoding = "utf_8" # according to the XML spec, this is the default
# this code successively tries to refine the default
# whenever it fails to refine, it falls back to
# the last place encoding was set.
encoding = "utf_8"
# according to the XML spec, this is the default this code successively tries to refine the default
# whenever it fails to refine, it falls back to the last place encoding was set.
if len(buffer) >= 4:
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
enc_info = autodetect_dict.get(bytes, None)
@@ -51,8 +51,7 @@ def autoDetectXMLEncoding(buffer):
enc_info = None
if enc_info:
encoding = enc_info # we've got a guess... these are
#the new defaults
encoding = enc_info # we've got a guess... these are the new defaults
# try to find a more precise encoding using xml declaration
secret_decoder_ring = codecs.lookup(encoding)[1]
@@ -77,4 +76,4 @@ def autoDetectXMLEncoding(buffer):
def decoder(buffer):
encoding = autoDetectXMLEncoding(buffer)
return buffer.decode(encoding).encode('utf8')
return to_unicode(buffer, charset=encoding)
+2 -2
View File
@@ -415,10 +415,10 @@ def fix_newlines(path):
|\r|
)''')
for filename in listdir(path, '.*\.(py|html)$', drop=False):
rdata = read_file(filename, 'rb')
rdata = read_file(filename, 'r')
wdata = regex.sub('\n', rdata)
if wdata != rdata:
write_file(filename, wdata, 'wb')
write_file(filename, wdata, 'w')
def copystream(
+23 -20
View File
@@ -13,7 +13,8 @@ Contains the classes for the global used variables:
- Session
"""
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, unicodeT, long
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \
unicodeT, long, hashlib_md5
from gluon.storage import Storage, List
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
from gluon.contenttype import contenttype
@@ -30,7 +31,7 @@ from gluon.fileutils import copystream
import hashlib
from pydal.contrib import portalocker
from pickle import Pickler, MARK, DICT, EMPTY_DICT
#from types import DictionaryType
# from types import DictionaryType
import datetime
import re
import os
@@ -48,7 +49,7 @@ PAST = 'Sat, 1-Jan-1971 00:00:00'
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
try:
#FIXME PY3
# FIXME PY3
from gluon.contrib.minify import minify
have_minify = True
except ImportError:
@@ -79,6 +80,7 @@ template_mapping = {
'js:inline': js_inline
}
# IMPORTANT:
# this is required so that pickled dict(s) and class.__dict__
# are sorted and web2py can detect without ambiguity when a session changes
@@ -89,9 +91,11 @@ class SortingPickler(Pickler):
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
if PY2:
#FIXME PY3
SortingPickler.dispatch = copy.copy(Pickler.dispatch)
SortingPickler.dispatch[dict] = SortingPickler.save_dict
else:
SortingPickler.dispatch_table = copyreg.dispatch_table.copy()
SortingPickler.dispatch_table[dict] = SortingPickler.save_dict
def sorting_dumps(obj, protocol=None):
@@ -119,7 +123,7 @@ def copystream_progress(request, chunk_size=10 ** 5):
dest = tempfile.NamedTemporaryFile()
except NotImplementedError: # and GAE this
dest = tempfile.TemporaryFile()
if not 'X-Progress-ID' in request.get_vars:
if 'X-Progress-ID' not in request.get_vars:
copystream(source, dest, size, chunk_size)
return dest
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
@@ -197,7 +201,8 @@ class Request(Storage):
"""Takes the QUERY_STRING and unpacks it to get_vars
"""
query_string = self.env.get('query_string', '')
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
dget = urlparse.parse_qs(query_string, keep_blank_values=1)
# Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
get_vars = self._get_vars = Storage(dget)
for (key, value) in iteritems(get_vars):
if isinstance(value, list) and len(value) == 1:
@@ -227,8 +232,7 @@ class Request(Storage):
body.seek(0)
# parse POST variables on POST, PUT, BOTH only in post_vars
if (body and not is_json
and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'):
query_string = env.pop('QUERY_STRING', None)
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
try:
@@ -349,14 +353,14 @@ class Request(Storage):
current.session.forget()
redirect(URL(scheme='https', args=self.args, vars=self.vars))
def restful(self):
def restful(self, ignore_extension=False):
def wrapper(action, request=self):
def f(_action=action, *a, **b):
request.is_restful = True
env = request.env
is_json = env.content_type=='application/json'
is_json = env.content_type == 'application/json'
method = env.request_method
if len(request.args) and '.' in request.args[-1]:
if not ignore_extension and len(request.args) and '.' in request.args[-1]:
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
current.response.headers['Content-Type'] = \
contenttype('.' + request.extension.lower())
@@ -415,7 +419,6 @@ class Response(Storage):
if not escape:
self.body.write(str(data))
else:
# FIXME PY3:
self.body.write(to_native(xmlescape(data)))
def render(self, *a, **b):
@@ -451,13 +454,13 @@ class Response(Storage):
for meta in iteritems((self.meta or {})):
k, v = meta
if isinstance(v, dict):
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), to_native(xmlescape(v[key]))) for key in v) +' />\n'
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key),
to_native(xmlescape(v[key]))) for key in v) + ' />\n'
else:
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
self.write(s, escape=False)
def include_files(self, extensions=None):
"""
Includes files (usually in the head).
Can minify and cache local files
@@ -484,8 +487,7 @@ class Response(Storage):
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
# cache for 5 minutes by default
key = hashlib.md5(repr(files)).hexdigest()
key = hashlib_md5(repr(files)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60 * 5)
def call_minify(files=files):
@@ -523,6 +525,7 @@ class Response(Storage):
tmpl = template_mapping.get(f)
if tmpl:
s.append(tmpl % item[1])
self.write(''.join(s), escape=False)
def stream(self,
@@ -578,9 +581,9 @@ class Response(Storage):
if hasattr(stream, 'name'):
filename = stream.name
if filename and not 'content-type' in keys:
if filename and 'content-type' not in keys:
headers['Content-Type'] = contenttype(filename)
if filename and not 'content-length' in keys:
if filename and 'content-length' not in keys:
try:
headers['Content-Length'] = \
os.path.getsize(filename)
@@ -1022,7 +1025,7 @@ class Session(Storage):
if self._forget:
del rcookies[response.session_id_name]
return
if self.get('httponly_cookies',True):
if self.get('httponly_cookies', True):
scookies['HttpOnly'] = True
if self._secure:
scookies['secure'] = True
@@ -1193,7 +1196,7 @@ class Session(Storage):
if (not response.session_id or
not response.session_filename or
self._forget
or self._unchanged(response)):
or self._unchanged(response)):
# self.clear_session_cookies()
return False
else:
+13 -13
View File
@@ -182,8 +182,8 @@ class Highlighter(object):
)),
'PYTHONMultilineString': (python_tokenizer,
(('ENDMULTILINESTRING',
re.compile(r'.*?("""|\'\'\')',
re.DOTALL), 'color: darkred'), )),
re.compile(r'.*?("""|\'\'\')',
re.DOTALL), 'color: darkred'), )),
'HTML': (html_tokenizer, (
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
@@ -209,7 +209,7 @@ class Highlighter(object):
mode = self.mode
while i < len(data):
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
if not token in self.suppress_tokens:
if token not in self.suppress_tokens:
match = o_re.match(data, i)
if match:
if style:
@@ -221,7 +221,7 @@ class Highlighter(object):
new_mode = \
Highlighter.all_styles[mode][0](self,
token, match, style)
if not new_mode is None:
if new_mode is not None:
mode = new_mode
i += max(1, len(match.group()))
break
@@ -241,9 +241,9 @@ class Highlighter(object):
style = self.styles[token]
if self.span_style != style:
if style != 'Keep':
if not self.span_style is None:
if self.span_style is not None:
self.output.append('</span>')
if not style is None:
if style is not None:
self.output.append('<span style="%s">' % style)
self.span_style = style
@@ -260,7 +260,7 @@ def highlight(
):
styles = styles or {}
attributes = attributes or {}
if not 'CODE' in styles:
if 'CODE' not in styles:
code_style = """
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
@@ -272,7 +272,7 @@ def highlight(
white-space: pre !important;\n"""
else:
code_style = styles['CODE']
if not 'LINENUMBERS' in styles:
if 'LINENUMBERS' not in styles:
linenumbers_style = """
font-size: 11px;
font-family: Bitstream Vera Sans Mono,monospace;
@@ -283,7 +283,7 @@ def highlight(
color: #A0A0A0;\n"""
else:
linenumbers_style = styles['LINENUMBERS']
if not 'LINEHIGHLIGHT' in styles:
if 'LINEHIGHLIGHT' not in styles:
linehighlight_style = "background-color: #EBDDE2;"
else:
linehighlight_style = styles['LINEHIGHLIGHT']
@@ -333,8 +333,9 @@ def highlight(
== '_' and value])
if fa:
fa = ' ' + fa
return '<table%s><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
% (fa, linenumbers_style, numbers, code_style, code)
return '<table%s><tr style="vertical-align:top;">' \
'<td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td>' \
'<td><pre style="%s">%s</pre></td></tr></table>' % (fa, linenumbers_style, numbers, code_style, code)
if __name__ == '__main__':
@@ -342,5 +343,4 @@ if __name__ == '__main__':
argfp = open(sys.argv[1])
data = argfp.read()
argfp.close()
print('<html><body>' + highlight(data, sys.argv[2])\
+ '</body></html>')
print('<html><body>' + highlight(data, sys.argv[2]) + '</body></html>')
+29 -40
View File
@@ -20,7 +20,8 @@ import urllib
import base64
from gluon import sanitizer, decoder
import itertools
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
from gluon.utils import local_html_escape
import marshal
@@ -109,6 +110,7 @@ __all__ = [
DEFAULT_PASSWORD_DISPLAY = '*' * 8
def xmlescape(data, quote=True):
"""
Returns an escaped string of the provided data
@@ -124,10 +126,9 @@ def xmlescape(data, quote=True):
if not(isinstance(data, (text_type, bytes))):
# i.e., integers
data=str(data)
data = str(data)
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
# ... and do the escaping
data = local_html_escape(data, quote)
return data
@@ -596,10 +597,10 @@ class XML(XmlComponent):
for A, IMG and BlockQuote).
The key is the tag; the value is a list of allowed attributes.
"""
if sanitize:
text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
if isinstance(text, unicodeT):
text = to_native(text.encode('utf8', 'xmlcharrefreplace'))
if sanitize:
text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
elif isinstance(text, bytes):
text = to_native(text)
elif not isinstance(text, str):
@@ -671,6 +672,7 @@ def XML_pickle(data):
return XML_unpickle, (marshal.dumps(str(data)),)
copyreg.pickle(XML, XML_pickle, XML_unpickle)
@implements_bool
class DIV(XmlComponent):
"""
@@ -998,9 +1000,9 @@ class DIV(XmlComponent):
if isinstance(c, XmlComponent):
s = c.flatten(render)
elif render:
s = render(str(c))
s = render(to_native(c))
else:
s = str(c)
s = to_native(c)
text += s
if render:
text = render(text, self.tag, self.attributes)
@@ -1281,7 +1283,6 @@ class __TAG__(XmlComponent):
def __getattr__(self, name):
if name[-1:] == '_':
name = name[:-1] + '/'
name=to_bytes(name)
return lambda *a, **b: __tag_div__(name, *a, **b)
def __call__(self, html):
@@ -1310,8 +1311,10 @@ class HTML(DIV):
tag = b'html'
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
transitional = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
frameset = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
transitional = \
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
frameset = \
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
html5 = b'<!DOCTYPE HTML>\n'
def xml(self):
@@ -1862,7 +1865,7 @@ class INPUT(DIV):
except:
import traceback
print(traceback.format_exc())
msg = "Validation error, field:%s %s" % (name,validator)
msg = "Validation error, field:%s %s" % (name, validator)
raise Exception(msg)
if errors is not None:
self.vars[name] = value
@@ -1912,7 +1915,7 @@ class INPUT(DIV):
name = self.attributes.get('_name', None)
if name and hasattr(self, 'errors') \
and self.errors.get(name, None) \
and self['hideerror'] != True:
and self['hideerror'] is not True:
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
return DIV.xml(self) + DIV(
DIV(
@@ -1980,7 +1983,6 @@ class OPTGROUP(DIV):
class SELECT(INPUT):
"""
Examples:
@@ -2015,7 +2017,7 @@ class SELECT(INPUT):
if value is not None:
if not self['_multiple']:
for c in options: # my patch
if ((value is not None) and (str(c['_value']) == str(value))):
if (value is not None) and (str(c['_value']) == str(value)):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2025,7 +2027,7 @@ class SELECT(INPUT):
else:
values = [str(value)]
for c in options: # my patch
if ((value is not None) and (str(c['_value']) in values)):
if (value is not None) and (str(c['_value']) in values):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2376,22 +2378,21 @@ class FORM(DIV):
def as_json(self, sanitize=True):
d = self.as_dict(flat=True, sanitize=sanitize)
from serializers import json
from gluon.serializers import json
return json(d)
def as_yaml(self, sanitize=True):
d = self.as_dict(flat=True, sanitize=sanitize)
from serializers import yaml
from gluon.serializers import yaml
return yaml(d)
def as_xml(self, sanitize=True):
d = self.as_dict(flat=True, sanitize=sanitize)
from serializers import xml
from gluon.serializers import xml
return xml(d)
class BEAUTIFY(DIV):
"""
Turns any list, dictionary, etc into decent looking html.
@@ -2548,7 +2549,7 @@ class MENU(DIV):
li['_class'] = li['_class'] + ' ' + self['li_active']
else:
li['_class'] = self['li_active']
if len(item) <= 4 or item[4] == True:
if len(item) <= 4 or item[4] is True:
ul.append(li)
return ul
@@ -2562,7 +2563,7 @@ class MENU(DIV):
# ex: ('', False, A('title', _href=URL(...), _title="title"))
# ex: (A('title', _href=URL(...), _title="title"), False, None)
custom_items.append(item)
elif len(item) <= 4 or item[4] == True:
elif len(item) <= 4 or item[4] is True:
select.append(OPTION(CAT(prefix, item[0]),
_value=item[2], _selected=item[1]))
if len(item) > 3 and len(item[3]):
@@ -2655,36 +2656,24 @@ class web2pyHTMLParser(HTMLParser):
"""
obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
obj.tree contains the root of the tree, and tree can be manipulated
>>> str(web2pyHTMLParser('hello<div a="b" c=3>wor&lt;ld<span>xxx</span>y<script/>yy</div>zzz').tree)
'hello<div a="b" c="3">wor&lt;ld<span>xxx</span>y<script></script>yy</div>zzz'
>>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
'<div>a<span>b</span></div>c'
>>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
>>> tree.element(_a='b')['_c']=5
>>> str(tree)
'hello<div a="b" c="5">world</div>'
"""
def __init__(self, text, closed=('input', 'link')):
HTMLParser.__init__(self)
self.tree = self.parent = TAG['']()
self.closed = closed
self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
self.last = None
self.feed(text)
def handle_starttag(self, tagname, attrs):
if tagname.upper() in self.tags:
tag = eval(tagname.upper())
else:
if tagname in self.closed:
tagname += '/'
tag = TAG[tagname]()
if tagname in self.closed:
tagname += '/'
tag = TAG[tagname]()
for key, value in attrs:
tag['_' + key] = value
tag.parent = self.parent
self.parent.append(tag)
if not tag.tag.endswith(b'/'):
if not tag.tag.endswith('/'):
self.parent = tag
else:
self.last = tag.tag[:-1]
@@ -2707,7 +2696,6 @@ class web2pyHTMLParser(HTMLParser):
self.parent.append(entitydefs[name])
def handle_endtag(self, tagname):
tagname = to_bytes(tagname)
# this deals with unbalanced tags
if tagname == self.last:
return
@@ -2717,7 +2705,8 @@ class web2pyHTMLParser(HTMLParser):
self.parent = self.parent.parent
except:
raise RuntimeError("unable to balance tag %s" % tagname)
if parent_tagname[:len(tagname)] == tagname: break
if parent_tagname[:len(tagname)] == tagname:
break
def markdown_serializer(text, tag=None, attr=None):
+5 -3
View File
@@ -11,7 +11,7 @@ HTTP statuses helpers
"""
import re
from gluon._compat import iteritems
from gluon._compat import iteritems, unicodeT
__all__ = ['HTTP', 'redirect']
@@ -116,12 +116,14 @@ class HTTP(Exception):
for k, v in iteritems(headers):
if isinstance(v, list):
rheaders += [(k, str(item)) for item in v]
elif not v is None:
elif v is not None:
rheaders.append((k, str(v)))
responder(status, rheaders)
if env.get('request_method', '') == 'HEAD':
return ['']
elif isinstance(body, (str, bytes, bytearray)):
if isinstance(body, unicodeT):
body = body.encode('utf-8')
return [body]
elif hasattr(body, '__iter__'):
return body
@@ -148,7 +150,7 @@ class HTTP(Exception):
web2py_error=self.headers.get('web2py_error'))
def __str__(self):
"stringify me"
"""stringify me"""
return self.message
+1 -5
View File
@@ -78,13 +78,9 @@ alert_dependency = ['hashlib', 'uuid']
# Now we remove the blacklisted modules if we are using the stated
# python version.
#
# List of modules deprecated in Python 2.6 or 2.7 that are in the above set
# List of modules deprecated in Python 2.7 that are in the above list
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
if python_version >= '2.6':
base_modules += ['json', 'multiprocessing']
base_modules = list(set(base_modules).difference(set(py26_deprecated)))
if python_version >= '2.7':
base_modules += ['argparse', 'json', 'multiprocessing']
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
+18 -10
View File
@@ -951,32 +951,40 @@ def findT(path, language=DEFAULT_LANGUAGE):
Note:
Must be run by the admin app
"""
from gluon.tools import Auth, Crud
lang_file = pjoin(path, 'languages', language + '.py')
sentences = read_dict(lang_file)
mp = pjoin(path, 'models')
cp = pjoin(path, 'controllers')
vp = pjoin(path, 'views')
mop = pjoin(path, 'modules')
def add_message(message):
if not message.startswith('#') and not '\n' in message:
tokens = message.rsplit('##', 1)
else:
# this allows markmin syntax in translations
tokens = [message]
if len(tokens) == 2:
message = tokens[0].strip() + '##' + tokens[1].strip()
if message and not message in sentences:
sentences[message] = message.replace("@markmin\x01", "")
for filename in \
listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
data = to_native(read_locked(filename))
items = regex_translate.findall(data)
items += ["@markmin\x01%s" %x for x in regex_translate_m.findall(data)]
for x in regex_translate_m.findall(data):
if x[0:3] in ["'''", '"""']: items.append("%s@markmin\x01%s" %(x[0:3], x[3:]))
else: items.append("%s@markmin\x01%s" %(x[0], x[1:]))
for item in items:
try:
message = safe_eval(item)
except:
continue # silently ignore inproperly formatted strings
if not message.startswith('#') and not '\n' in message:
tokens = message.rsplit('##', 1)
else:
# this allows markmin syntax in translations
tokens = [message]
if len(tokens) == 2:
message = tokens[0].strip() + '##' + tokens[1].strip()
if message and not message in sentences:
sentences[message] = message.replace("@markmin\x01", "")
add_message(message)
gluon_msg = [Auth.default_messages, Crud.default_messages]
for item in [x for m in gluon_msg for x in m.values() if x is not None]:
add_message(item)
if not '!langcode!' in sentences:
sentences['!langcode!'] = (
DEFAULT_LANGUAGE if language in ('default', DEFAULT_LANGUAGE) else language)
+20 -21
View File
@@ -11,7 +11,8 @@ The gluon wsgi application
"""
from __future__ import print_function
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
if False:
import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
import gc
import os
@@ -26,7 +27,7 @@ import random
import string
from gluon._compat import Cookie, urllib2
#from thread import allocate_lock
# from thread import allocate_lock
from gluon.fileutils import abspath, write_file
from gluon.settings import global_settings
@@ -67,14 +68,14 @@ import gluon.messageboxhandler
logging.gluon = gluon
# so we must restore it! Thanks ozancag
import locale
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
exists = os.path.exists
pjoin = os.path.join
try:
logging.config.fileConfig(abspath("logging.conf"))
except: # fails on GAE or when logfile is missing
except: # fails on GAE or when logfile is missing
logging.basicConfig()
logger = logging.getLogger("web2py")
@@ -254,6 +255,7 @@ class LazyWSGI(object):
return [data]
for item in middleware_apps:
app = item(app)
def caller(app):
return app(self.environ, self.start_response)
return lambda caller=caller, app=app: caller(app)
@@ -294,9 +296,9 @@ def wsgibase(environ, responder):
response = Response()
session = Session()
env = request.env
#env.web2py_path = global_settings.applications_parent
# env.web2py_path = global_settings.applications_parent
env.web2py_version = web2py_version
#env.update(global_settings)
# env.update(global_settings)
static_file = False
http_response = None
try:
@@ -325,7 +327,6 @@ def wsgibase(environ, responder):
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
response.stream(static_file, request=request)
# ##################################################
# fill in request items
# ##################################################
@@ -356,17 +357,15 @@ def wsgibase(environ, responder):
cmd_opts = global_settings.cmd_options
request.update(
client = client,
folder = abspath('applications', app) + os.sep,
ajax = x_req_with == 'xmlhttprequest',
cid = env.http_web2py_component_element,
is_local = (env.remote_addr in local_hosts and
client == env.remote_addr),
is_shell = False,
is_scheduler = False,
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
or env.https == 'on'
client=client,
folder=abspath('applications', app) + os.sep,
ajax=x_req_with == 'xmlhttprequest',
cid=env.http_web2py_component_element,
is_local=(env.remote_addr in local_hosts and client == env.remote_addr),
is_shell=False,
is_scheduler=False,
is_https=env.wsgi_url_scheme in HTTPS_SCHEMES or
request.env.http_x_forwarded_proto in HTTPS_SCHEMES or env.https == 'on'
)
request.url = environ['PATH_INFO']
@@ -390,7 +389,7 @@ def wsgibase(environ, responder):
% 'invalid request',
web2py_error='invalid application')
elif not request.is_local and exists(disabled):
five0three = os.path.join(request.folder,'static','503.html')
five0three = os.path.join(request.folder, 'static', '503.html')
if os.path.exists(five0three):
raise HTTP(503, file(five0three, 'r').read())
else:
@@ -406,7 +405,7 @@ def wsgibase(environ, responder):
# get the GET and POST data
# ##################################################
#parse_get_post_vars(request, environ)
# parse_get_post_vars(request, environ)
# ##################################################
# expose wsgi hooks for convenience
@@ -625,7 +624,7 @@ def appfactory(wsgiapp=wsgibase,
raise BaseException("Can't create dir %s" % profiler_dir)
filepath = pjoin(profiler_dir, 'wtest')
try:
filehandle = open( filepath, 'w' )
filehandle = open(filepath, 'w')
filehandle.close()
os.unlink(filepath)
except IOError:
+9 -6
View File
@@ -19,13 +19,14 @@ import sched
import re
import datetime
import platform
import gluon.fileutils
from functools import reduce
try:
import cPickle as pickle
except:
import pickle
from gluon.settings import global_settings
from gluon import fileutils
from gluon._compat import to_bytes
from pydal.contrib import portalocker
logger = logging.getLogger("web2py.cron")
@@ -116,7 +117,7 @@ class Token(object):
def __init__(self, path):
self.path = os.path.join(path, 'cron.master')
if not os.path.exists(self.path):
fileutils.write_file(self.path, '', 'wb')
fileutils.write_file(self.path, to_bytes(''), 'wb')
self.master = None
self.now = time.time()
@@ -139,7 +140,7 @@ class Token(object):
if portalocker.LOCK_EX is None:
logger.warning('WEB2PY CRON: Disabled because no file locking')
return None
self.master = open(self.path, 'rb+')
self.master = fileutils.open_file(self.path, 'rb+')
try:
ret = None
portalocker.lock(self.master, portalocker.LOCK_EX)
@@ -167,6 +168,7 @@ class Token(object):
"""
Writes into cron.master the time when cron job was completed
"""
ret = self.master.closed
if not self.master.closed:
portalocker.lock(self.master, portalocker.LOCK_EX)
logger.debug('WEB2PY CRON: Releasing cron lock')
@@ -177,6 +179,7 @@ class Token(object):
pickle.dump((self.now, time.time()), self.master)
portalocker.unlock(self.master)
self.master.close()
return ret
def rangetolist(s, period='min'):
@@ -222,8 +225,8 @@ def parsecronline(line):
params = line.strip().split(None, 6)
if len(params) < 7:
return None
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4,
'fri': 5, 'sat': 6}
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3,
'thu': 4, 'fri': 5, 'sat': 6}
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
if not s in [None, '*']:
task[id] = []
@@ -236,7 +239,7 @@ def parsecronline(line):
elif val.isdigit() or val == '-1':
task[id].append(int(val))
elif id == 'dow' and val[:3].lower() in daysofweek:
task[id].append(daysofweek(val[:3].lower()))
task[id].append(daysofweek[val[:3].lower()])
task['user'] = params[5]
task['cmd'] = params[6]
return task
+4 -1
View File
@@ -137,7 +137,10 @@ class RestrictedError(Exception):
self.environment = environment
if layer:
try:
self.traceback = traceback.format_exc()
try:
self.traceback = traceback.format_exc()
except:
self.traceback = traceback.format_exc(limit=1)
except:
self.traceback = 'no traceback because template parsing error'
try:
+3 -4
View File
@@ -206,8 +206,7 @@ def url_out(request, environ, application, controller, function,
if host is True or (host is None and (scheme or port is not None)):
host = request.env.http_host
if not scheme or scheme is True:
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
if request else 'http'
scheme = request.env.get('wsgi_url_scheme', 'http').lower() if request else 'http'
if host:
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
url = '%s://%s%s' % (scheme, host_port, url)
@@ -317,7 +316,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
symbols = dict(app=app)
try:
exec(data + '\n', symbols)
exec(data, symbols)
except SyntaxError as e:
logger.error(
'%s has a syntax error and will not be loaded\n' % path
@@ -1040,7 +1039,7 @@ class MapUrlIn(object):
else:
default_function = self.router.default_function # str or None
default_function = self.domain_function or default_function
if not arg0 or functions and arg0 not in functions:
if not arg0 or functions and arg0.split('.')[0] not in functions:
self.function = default_function or ""
self.pop_arg_if(arg0 and self.function == arg0)
else:
+4 -5
View File
@@ -307,13 +307,13 @@ try:
except ImportError:
has_futures = False
class Future:
class Future(object):
pass
class ThreadPoolExecutor:
class ThreadPoolExecutor(object):
pass
class _WorkItem:
class _WorkItem(object):
pass
@@ -784,8 +784,7 @@ class Rocket(object):
the application developer. Please update your \
applications to no longer call rocket.stop(True)"
try:
import warnings
raise warnings.DeprecationWarning(msg)
raise DeprecationWarning(msg)
except ImportError:
raise RuntimeError(msg)
+2 -2
View File
@@ -11,7 +11,7 @@ Cross-site scripting (XSS) defense
"""
from gluon._compat import HTMLParser, urlparse, entitydefs, basestring
from cgi import escape
from gluon.utils import local_html_escape
from formatter import AbstractFormatter
from xml.sax.saxutils import quoteattr
@@ -21,7 +21,7 @@ __all__ = ['sanitize']
def xssescape(text):
"""Gets rid of < and > and & and, for good measure, :"""
return escape(text, quote=True).replace(':', '&#58;')
return local_html_escape(text, quote=True).replace(':', '&#58;')
class XssCleaner(HTMLParser):
+2 -2
View File
@@ -1158,7 +1158,7 @@ class Scheduler(MetaScheduler):
if not self.db_thread:
logger.debug('thread building own DAL object')
self.db_thread = DAL(
self.db._uri, folder=self.db._adapter.folder)
self.db._uri, folder=self.db._adapter.folder, decode_credentials=True)
self.define_tables(self.db_thread, migrate=False)
try:
db = self.db_thread
@@ -1698,7 +1698,7 @@ def main():
print('groups for this worker: ' + ', '.join(group_names))
print('connecting to database in folder: ' + options.db_folder or './')
print('using URI: ' + options.db_uri)
db = DAL(options.db_uri, folder=options.db_folder)
db = DAL(options.db_uri, folder=options.db_folder, decode_credentials=True)
print('instantiating scheduler...')
scheduler = Scheduler(db=db,
worker_name=options.worker_name,
+39 -31
View File
@@ -29,7 +29,7 @@ from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from pydal.base import DEFAULT
from pydal.objects import Table, Row, Expression, Field, Set, Rows
from pydal.adapters.base import CALLABLETYPES
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref, merge_tablemaps
from pydal.helpers.classes import Reference, SQLCustomType
from gluon.storage import Storage
from gluon.utils import md5_hash
@@ -405,7 +405,7 @@ class RadioWidget(OptionsWidget):
cols = attributes.get('cols', 1)
totals = len(options)
mods = totals % cols
rows = totals / cols
rows = totals // cols
if mods:
rows += 1
@@ -471,7 +471,7 @@ class CheckboxesWidget(OptionsWidget):
cols = attributes.get('cols', 1)
totals = len(options)
mods = totals % cols
rows = totals / cols
rows = totals // cols
if mods:
rows += 1
@@ -684,9 +684,10 @@ class AutocompleteWidget(object):
urlvars = request.vars
urlvars[default_var] = 1
self.url = URL(args=request.args, vars=urlvars)
self.callback()
self.run_callback = True
else:
self.url = request
self.run_callback = False
def callback(self):
if self.keyword in self.request.vars:
@@ -759,6 +760,8 @@ class AutocompleteWidget(object):
raise HTTP(200, '')
def __call__(self, field, value, **attributes):
if self.run_callback:
self.callback()
default = dict(
_type='text',
value=(value is not None and str(value)) or '',
@@ -1916,7 +1919,7 @@ class SQLFORM(FORM):
if 'table_name' in attributes:
del attributes['table_name']
return SQLFORM(DAL(None).define_table(table_name, *fields),
return SQLFORM(DAL(None).define_table(table_name, *[field.clone() for field in fields]),
**attributes)
@staticmethod
@@ -2153,7 +2156,8 @@ class SQLFORM(FORM):
ignore_common_filters=None,
auto_pagination=True,
use_cursor=False,
represent_none=None):
represent_none=None,
showblobs=False):
formstyle = formstyle or current.response.formstyle
if isinstance(query, Set):
@@ -2194,7 +2198,7 @@ class SQLFORM(FORM):
buttondelete='icon trash icon-trash glyphicon glyphicon-trash',
buttonedit='icon pen icon-pencil glyphicon glyphicon-pencil',
buttontable='icon rightarrow icon-arrow-right glyphicon glyphicon-arrow-right',
buttonview='icon magnifier icon-zoom-in glyphicon glyphicon-zoom-in',
buttonview='icon magnifier icon-zoom-in glyphicon glyphicon-zoom-in'
)
elif not isinstance(ui, dict):
raise RuntimeError('SQLFORM.grid ui argument must be a dictionary')
@@ -2328,7 +2332,7 @@ class SQLFORM(FORM):
if not isinstance(left, (list, tuple)):
left = [left]
for join in left:
tablenames += db._adapter.tables(join)
tablenames = merge_tablemaps(tablenames, db._adapter.tables(join))
tables = [db[tablename] for tablename in tablenames]
if fields:
# add missing tablename to virtual fields
@@ -2340,7 +2344,7 @@ class SQLFORM(FORM):
else:
fields = []
columns = []
filter1 = lambda f: isinstance(f, Field) and f.type != 'blob'
filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs)
filter2 = lambda f: isinstance(f, Field) and f.readable
for table in tables:
fields += filter(filter1, table)
@@ -2553,6 +2557,7 @@ class SQLFORM(FORM):
if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
expcolumns.append(str(field))
expcolumns = ['"%s"' % '"."'.join(f.split('.')) for f in expcolumns]
if export_type in exportManager and exportManager[export_type]:
if keywords:
try:
@@ -2862,7 +2867,7 @@ class SQLFORM(FORM):
for field in columns:
if not field.readable:
continue
if field.type == 'blob':
elif field.type == 'blob' and not showblobs:
continue
if isinstance(field, Field.Virtual) and field.tablename in row:
try:
@@ -3150,7 +3155,9 @@ class SQLFORM(FORM):
# if isinstance(linked_tables, dict):
# linked_tables = linked_tables.get(table._tablename, [])
if linked_tables is None or referee in linked_tables:
field.represent = lambda id, r=None, referee=referee, rep=field.represent: A(callable(rep) and rep(id) or id, cid=request.cid, _href=url(args=['view', referee, id]))
field.represent = (lambda id, r=None, referee=referee, rep=field.represent:
A(callable(rep) and rep(id) or id,
cid=request.cid, _href=url(args=['view', referee, id])))
except (KeyError, ValueError, TypeError):
redirect(URL(args=table._tablename))
if nargs == len(args) + 1:
@@ -3311,23 +3318,28 @@ class SQLTABLE(TABLE):
if not sqlrows:
return
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields))
tablemap = dict(((f.tablename, f.table) for f in fieldmap.values()))
for table in tablemap.values():
pref = table._tablename + '.'
fieldmap.update(((pref+f.name, f) for f in table._virtual_fields))
fieldmap.update(((pref+f.name, f) for f in table._virtual_methods))
field_types = (Field, Field.Virtual, Field.Method)
if not columns:
columns = list(sqlrows.colnames)
if headers == 'fieldname:capitalize':
header_func = {
'fieldname:capitalize': lambda f: f.name.replace('_', ' ').title(),
'labels': lambda f: f.label
}
if isinstance(headers, str) and headers in header_func:
make_name = header_func[headers]
headers = {}
for c in columns:
tfmatch = REGEX_TABLE_DOT_FIELD.match(c)
if tfmatch:
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups()
headers[t + '.' + f] = f.replace('_', ' ').title()
f = fieldmap.get(c)
if isinstance(f, field_types):
headers[c] = make_name(f)
else:
headers[c] = REGEX_ALIAS_MATCH.sub(r'\2', c)
elif headers == 'labels':
headers = {}
for c in columns:
(t, f) = c.split('.')
field = sqlrows.db[t][f]
headers[c] = field.label
if colgroup:
cols = [COL(_id=c.replace('.', '-'), data={'column': i + 1})
for i, c in enumerate(columns)]
@@ -3380,9 +3392,8 @@ class SQLTABLE(TABLE):
_class += ' rowselected'
for colname in columns:
matched_column_field = \
sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD.match(colname)
if not matched_column_field:
field = fieldmap.get(colname)
if not isinstance(field, field_types):
if "_extra" in record and colname in record._extra:
r = record._extra[colname]
row.append(TD(r))
@@ -3390,12 +3401,9 @@ class SQLTABLE(TABLE):
else:
raise KeyError(
"Column %s not found (SQLTABLE)" % colname)
(tablename, fieldname) = matched_column_field.groups()
colname = tablename + '.' + fieldname
try:
field = sqlrows.db[tablename][fieldname]
except (KeyError, AttributeError):
field = None
# Virtual fields don't have parent table name...
tablename = colname.split('.', 1)[0]
fieldname = field.name
if tablename in record \
and isinstance(record, Row) \
and isinstance(record[tablename], Row):
+3 -1
View File
@@ -13,6 +13,7 @@ from .test_contribs import *
from .test_routes import *
from .test_router import *
from .test_validators import *
from .test_authapi import *
from .test_tools import *
from .test_utils import *
from .test_serializers import *
@@ -22,7 +23,8 @@ from .test_appadmin import *
from .test_web import *
from .test_sqlhtml import *
from .test_scheduler import *
from .test_cron import *
from .test_is_url import *
if sys.version[:3] == '2.7':
from .test_is_url import *
from .test_old_doctests import *
+24 -4
View File
@@ -17,6 +17,7 @@ from gluon import fileutils
from gluon.dal import DAL, Field, Table
from gluon.http import HTTP
from gluon.fileutils import open_file
from gluon.cache import CacheInRam
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
@@ -91,7 +92,8 @@ class TestAppAdmin(unittest.TestCase):
self.run_view()
self.run_view_file_stream()
except Exception as e:
print(e.message)
import traceback
print(traceback.format_exc())
self.fail('Could not make the view')
def test_index(self):
@@ -103,6 +105,21 @@ class TestAppAdmin(unittest.TestCase):
self._test_index()
remove_compiled_application(appname_path)
def test_index_minify(self):
# test for gluon/contrib/minify
self.env['response'].optimize_css = 'concat|minify'
self.env['response'].optimize_js = 'concat|minify'
self.env['current'].cache = Storage({'ram':CacheInRam()})
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
self._test_index()
file_l = os.listdir(os.path.join(appname_path, 'static', 'temp'))
file_l.sort()
self.assertTrue(len(file_l) == 2)
self.assertEqual(file_l[0][0:10], 'compressed')
self.assertEqual(file_l[1][0:10], 'compressed')
self.assertEqual(file_l[0][-3:], 'css')
self.assertEqual(file_l[1][-2:], 'js')
def test_select(self):
request = self.env['request']
request.args = List(['db'])
@@ -115,7 +132,8 @@ class TestAppAdmin(unittest.TestCase):
try:
self.run_view()
except Exception as e:
print(e.message)
import traceback
print(traceback.format_exc())
self.fail('Could not make the view')
def test_insert(self):
@@ -129,7 +147,8 @@ class TestAppAdmin(unittest.TestCase):
try:
self.run_view()
except Exception as e:
print(e.message)
import traceback
print(traceback.format_exc())
self.fail('Could not make the view')
def test_insert_submit(self):
@@ -151,7 +170,8 @@ class TestAppAdmin(unittest.TestCase):
try:
self.run_view()
except Exception as e:
print(e.message)
import traceback
print(traceback.format_exc())
self.fail('Could not make the view')
db = self.env['db']
lisa_record = db(db.auth_user.username == 'lisasimpson').select().first()
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Unit tests for authapi """
import os
import unittest
from gluon.globals import Request, Response, Session
from gluon.languages import translator
from gluon.dal import DAL, Field
from gluon.authapi import AuthAPI
from gluon.storage import Storage
from gluon._compat import to_bytes, to_native, add_charset
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
class TestAuthAPI(unittest.TestCase):
def setUp(self):
self.request = Request(env={})
self.request.application = 'a'
self.request.controller = 'c'
self.request.function = 'f'
self.request.folder = 'applications/admin'
self.response = Response()
self.session = Session()
T = translator('', 'en')
self.session.connect(self.request, self.response)
from gluon.globals import current
self.current = current
self.current.request = self.request
self.current.response = self.response
self.current.session = self.session
self.current.T = T
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
self.auth = AuthAPI(self.db)
self.auth.define_tables(username=True, signature=False)
# Create a user
self.auth.table_user().validate_and_insert(first_name='Bart',
last_name='Simpson',
username='bart',
email='bart@simpson.com',
password='bart_password',
registration_key='',
registration_id=''
)
self.db.commit()
def test_login(self):
result = self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
self.assertTrue(self.auth.is_logged_in())
self.assertTrue(result['user']['email'] == 'bart@simpson.com')
self.auth.logout()
self.assertFalse(self.auth.is_logged_in())
self.auth.settings.username_case_sensitive = False
result = self.auth.login(**{'username': 'BarT', 'password': 'bart_password'})
self.assertTrue(self.auth.is_logged_in())
def test_logout(self):
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
self.assertTrue(self.auth.is_logged_in())
result = self.auth.logout()
self.assertTrue(not self.auth.is_logged_in())
self.assertTrue(result['user'] is None)
def test_register(self):
self.auth.settings.login_after_registration = True
result = self.auth.register(**{
'username': 'lisa',
'first_name': 'Lisa',
'last_name': 'Simpson',
'email': 'lisa@simpson.com',
'password': 'lisa_password'
})
self.assertTrue(result['user']['email'] == 'lisa@simpson.com')
self.assertTrue(self.auth.is_logged_in())
with self.assertRaises(AssertionError): # Can't register if you're logged in
result = self.auth.register(**{
'username': 'lisa',
'first_name': 'Lisa',
'last_name': 'Simpson',
'email': 'lisa@simpson.com',
'password': 'lisa_password'
})
self.auth.logout()
self.auth.settings.login_after_registration = False
result = self.auth.register(**{
'username': 'barney',
'first_name': 'Barney',
'last_name': 'Gumble',
'email': 'barney@simpson.com',
'password': 'barney_password'
})
self.assertTrue(result['user']['email'] == 'barney@simpson.com')
self.assertFalse(self.auth.is_logged_in())
self.auth.settings.login_userfield = 'email'
result = self.auth.register(**{
'username': 'lisa',
'first_name': 'Lisa',
'last_name': 'Simpson',
'email': 'lisa@simpson.com',
'password': 'lisa_password'
})
self.assertTrue(result['errors']['email'] == self.auth.messages.email_taken)
self.assertTrue(result['user'] is None)
self.auth.settings.registration_requires_verification = True
result = self.auth.register(**{
'username': 'homer',
'first_name': 'Homer',
'last_name': 'Simpson',
'email': 'homer@simpson.com',
'password': 'homer_password'
})
self.assertTrue('key' in result['user'])
def test_profile(self):
with self.assertRaises(AssertionError):
# We are not logged in
self.auth.profile()
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
self.assertTrue(self.auth.is_logged_in())
result = self.auth.profile(email='bartolo@simpson.com')
self.assertTrue(result['user']['email'] == 'bartolo@simpson.com')
self.assertTrue(self.auth.table_user()[result['user']['id']].email == 'bartolo@simpson.com')
def test_change_password(self):
with self.assertRaises(AssertionError):
# We are not logged in
self.auth.change_password()
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
self.assertTrue(self.auth.is_logged_in())
self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
self.auth.logout()
self.assertTrue(not self.auth.is_logged_in())
self.auth.login(username='bart', password='1234')
self.assertTrue(self.auth.is_logged_in())
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='5678')
self.assertTrue('new_password2' in result['errors'])
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
self.assertTrue('old_password' in result['errors'])
def test_verify_key(self):
self.auth.settings.registration_requires_verification = True
result = self.auth.register(**{
'username': 'homer',
'first_name': 'Homer',
'last_name': 'Simpson',
'email': 'homer@simpson.com',
'password': 'homer_password'
})
self.assertTrue('key' in result['user'])
homer_id = result['user']['id']
homers_key = result['user']['key']
result = self.auth.verify_key(key=None)
self.assertTrue(result['errors'] is not None)
result = self.auth.verify_key(key='12345')
self.assertTrue(result['errors'] is not None)
result = self.auth.verify_key(key=homers_key)
self.assertTrue(result['errors'] is None)
self.assertEqual(self.auth.table_user()[homer_id].registration_key, '')
self.auth.settings.registration_requires_approval = True
result = self.auth.register(**{
'username': 'lisa',
'first_name': 'Lisa',
'last_name': 'Simpson',
'email': 'lisa@simpson.com',
'password': 'lisa_password'
})
lisa_id = result['user']['id']
result = self.auth.verify_key(key=result['user']['key'])
self.assertEqual(self.auth.table_user()[lisa_id].registration_key, 'pending')
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Unit tests for cron """
import unittest, os
from gluon.newcron import Token, crondance
class TestCron(unittest.TestCase):
def test_Token(self):
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
t = Token(path=appname_path)
self.assertNotEqual(t.acquire(), None)
self.assertFalse(t.release())
self.assertEqual(t.acquire(), None)
self.assertTrue(t.release())
return
def test_crondance(self):
#TODO update crondance to return something
crondance(os.getcwd())
+5 -1
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import unittest
import datetime
from gluon.fileutils import parse_version
from gluon.fileutils import parse_version, fix_newlines
class TestFileUtils(unittest.TestCase):
@@ -22,3 +23,6 @@ class TestFileUtils(unittest.TestCase):
# 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)))
def test_fix_newlines(self):
fix_newlines(os.path.dirname(os.path.abspath(__file__)))
+72 -24
View File
@@ -10,27 +10,76 @@ import re
import unittest
from gluon.globals import Request, Response, Session
from gluon.rewrite import regex_url_in
from gluon import URL
from gluon._compat import basestring
def setup_clean_session():
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
session.connect(request, response)
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
return current
class testRequest(unittest.TestCase):
def setUp(self):
from gluon.globals import current
current.request = request
current.response = response
current.session = session
return current
current.response = Response()
def test_restful_simple(self):
env = {'request_method': 'GET', 'PATH_INFO': '/welcome/default/index/1.pdf'}
r = Request(env)
regex_url_in(r, env)
@r.restful()
def simple_rest():
def GET(*args, **vars):
return args[0]
return locals()
self.assertEqual(simple_rest(), '1')
def test_restful_calls_post(self):
env = {'request_method': 'POST', 'PATH_INFO': '/welcome/default/index'}
r = Request(env)
regex_url_in(r, env)
@r.restful()
def post_rest():
def POST(*args, **vars):
return 'I posted'
return locals()
self.assertEqual(post_rest(), 'I posted')
def test_restful_ignore_extension(self):
env = {'request_method': 'GET', 'PATH_INFO': '/welcome/default/index/127.0.0.1'}
r = Request(env)
regex_url_in(r, env)
@r.restful(ignore_extension=True)
def ignore_rest():
def GET(*args, **vars):
return args[0]
return locals()
self.assertEqual(ignore_rest(), '127.0.0.1')
class testResponse(unittest.TestCase):
#port from python 2.7, needed for 2.5 and 2.6 tests
# port from python 2.7, needed for 2.5 and 2.6 tests
def assertRegexpMatches(self, text, expected_regexp, msg=None):
"""Fail the test unless the text matches the regular expression."""
if isinstance(expected_regexp, basestring):
@@ -99,8 +148,8 @@ class testResponse(unittest.TestCase):
response.files.append(URL('a', 'static', 'css/file.css'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
@@ -109,12 +158,11 @@ class testResponse(unittest.TestCase):
response.files.append(URL('a', 'static', 'css/file.ts'))
content = return_includes(response)
self.assertEqual(content,
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
)
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
)
response = Response()
response.files.append(URL('a', 'static', 'css/file.js'))
@@ -122,13 +170,13 @@ class testResponse(unittest.TestCase):
content = return_includes(response, extensions=['css'])
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
#regr test for #628
# regr test for #628
response = Response()
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
content = return_includes(response)
self.assertEqual(content, '')
#regr test for #628
# regr test for #628
response = Response()
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
content = return_includes(response)
@@ -147,7 +195,7 @@ class testResponse(unittest.TestCase):
def test_cookies(self):
current = setup_clean_session()
cookie = str(current.response.cookies)
session_key='%s=%s'%(current.response.session_id_name,current.response.session_id)
session_key = '%s=%s' % (current.response.session_id_name, current.response.session_id)
self.assertRegexpMatches(cookie, r'^Set-Cookie: ')
self.assertTrue(session_key in cookie)
self.assertTrue('Path=/' in cookie)
+50 -13
View File
@@ -11,11 +11,13 @@ import unittest
from gluon.html import A, ASSIGNJS, B, BEAUTIFY, P, BODY, BR, BUTTON, CAT, CENTER, CODE, COL, COLGROUP, DIV, SPAN, URL, verifyURL
from gluon.html import truncate_string, EM, FIELDSET, FORM, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML, I, IFRAME, IMG, INPUT, EMBED
from gluon.html import LABEL, LEGEND, LI, LINK, MARKMIN, MENU, META, OBJECT, OL, OPTGROUP, OPTION, PRE, SCRIPT, SELECT, STRONG
from gluon.html import STYLE, TABLE, TR, TD, TAG, TBODY, THEAD, TEXTAREA, TFOOT, TH, TITLE, TT, UL, XHTML, XML
from gluon.html import STYLE, TABLE, TR, TD, TAG, TBODY, THEAD, TEXTAREA, TFOOT, TH, TITLE, TT, UL, XHTML, XML, web2pyHTMLParser
from gluon.storage import Storage
from gluon.html import XML_pickle, XML_unpickle
from gluon.html import TAG_pickler, TAG_unpickler
from gluon._compat import xrange, PY2, to_native
from gluon.decoder import decoder
import re
class TestBareHelpers(unittest.TestCase):
@@ -155,7 +157,7 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(rtn, True)
# TODO: def test_XmlComponent(self):
@unittest.skipIf(not PY2, "Skipping Python 3.x tests for XML.__repr__")
def test_XML(self):
# sanitization process
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
@@ -168,6 +170,8 @@ class TestBareHelpers(unittest.TestCase):
# seams that __repr__ is no longer enough
##self.assertEqual(XML('1.3'), '1.3')
self.assertEqual(XML(u'<div>è</div>').xml(), b'<div>\xc3\xa8</div>')
# make sure unicode works with sanitize
self.assertEqual(XML(u'<div>è</div>', sanitize=True).xml(), b'<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'))
@@ -179,19 +183,18 @@ class TestBareHelpers(unittest.TestCase):
# 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>'))
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True).__repr__(),
XML('<h1>HelloWorld</h1>').__repr__())
# 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 />'))
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True).xml(),
XML('<p>Test</p><br /><p>Test</p><br />').xml())
# 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>')
@unittest.skipIf(not PY2, "Skipping Python 3.x tests for XML_unpickle.__repr__")
def test_XML_pickle_unpickle(self):
# weird test
self.assertEqual(XML_unpickle(XML_pickle('data to be pickle')[1][0]), 'data to be pickle')
self.assertEqual(str(XML_unpickle(XML_pickle('data to be pickle')[1][0])), 'data to be pickle')
def test_DIV(self):
# Empty DIV()
@@ -255,6 +258,11 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(DIV('<p>Test</p>', _class="class_test").get('_class'), 'class_test')
self.assertEqual(DIV(b'a').xml(), b'<div>a</div>')
def test_decoder(self):
tag_html = '<div><span><a id="1-1" u:v="$">hello</a></span><p class="this is a test">world</p></div>'
a = decoder(tag_html)
self.assertEqual(a, tag_html)
def test_CAT(self):
# Empty CAT()
self.assertEqual(CAT().xml(), b'')
@@ -636,8 +644,8 @@ class TestBareHelpers(unittest.TestCase):
# 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>')
# TODO check tags content
self.assertEqual(len(FORM('<>', _a='1', _b='2').as_xml()), 334)
def test_BEAUTIFY(self):
#self.assertEqual(BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml(),
@@ -670,13 +678,42 @@ class TestBareHelpers(unittest.TestCase):
# TODO: def test_embed64(self):
# TODO: def test_web2pyHTMLParser(self):
def test_web2pyHTMLParser(self):
#tag should not be a byte
self.assertEqual(web2pyHTMLParser("<div></div>").tree.components[0].tag, 'div')
a = str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
self.assertEqual(a, "<div>a<span>b</span></div>c")
tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
tree.element(_a='b')['_c']=5
self.assertEqual(str(tree), 'hello<div a="b" c="5">world</div>')
a = str(web2pyHTMLParser('<div><img class="img"/></div>', closed=['img']).tree)
self.assertEqual(a, '<div><img class="img" /></div>')
#greater-than sign ( > ) --> decimal &#62; --> hexadecimal &#x3E;
#Less-than sign ( < ) --> decimal &#60; --> hexadecimal &#x3C;
# test decimal
a = str(web2pyHTMLParser('<div>&#60; &#62;</div>').tree)
self.assertEqual(a, '<div>&lt; &gt;</div>')
# test hexadecimal
a = str(web2pyHTMLParser('<div>&#x3C; &#x3E;</div>').tree)
self.assertEqual(a, '<div>&lt; &gt;</div>')
def test_markdown(self):
def markdown(text, tag=None, attributes={}):
r = {None: re.sub('\s+',' ',text), \
'h1':'#'+text+'\\n\\n', \
'p':text+'\\n'}.get(tag,text)
return r
a=TAG('<h1>Header</h1><p>this is a test</p>')
ret = a.flatten(markdown)
self.assertEqual(ret, '#Header\\n\\nthis is a test\\n')
# TODO: def test_markdown_serializer(self):
# TODO: def test_markmin_serializer(self):
@unittest.skipIf(not PY2, "Skipping Python 3.x tests for MARKMIN")
def test_MARKMIN(self):
# This test pass with python 2.7 but expected to fail under 2.6
# with self.assertRaises(TypeError) as cm:
+39 -39
View File
@@ -586,81 +586,81 @@ class TestUnicode(unittest.TestCase):
# disables prepending the scheme in the return value
def testUnicodeToAsciiUrl(self):
self.assertEquals(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu')
self.assertEquals(
self.assertEqual(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu')
self.assertEqual(
unicode_to_ascii_authority(u'www.benn.ca'), 'www.benn.ca')
self.assertRaises(UnicodeError, unicode_to_ascii_authority,
u'\u4e2d' * 1000) # label is too long
def testValidUrls(self):
self.assertEquals(self.x(u'www.Alliancefrancaise.nu'), (
self.assertEqual(self.x(u'www.Alliancefrancaise.nu'), (
'http://www.Alliancefrancaise.nu', None))
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu'), (
self.assertEqual(self.x(u'www.Alliancefran\xe7aise.nu'), (
'http://www.xn--alliancefranaise-npb.nu', None))
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu:8080'), (
self.assertEqual(self.x(u'www.Alliancefran\xe7aise.nu:8080'), (
'http://www.xn--alliancefranaise-npb.nu:8080', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu'),
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu'),
('http://www.xn--alliancefranaise-npb.nu', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None))
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
self.assertEquals(self.x(
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None))
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None))
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None))
self.assertEqual(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
self.assertEqual(self.x(
u'http://\u4e2d\u4fd4.com'), ('http://xn--fiq13b.com', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'),
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'),
('http://xn--fiq13b.com/%4e%86', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None))
self.assertEquals(
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None))
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None))
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None))
self.assertEqual(
self.x(u'http://B\xfccher.ch'), ('http://xn--bcher-kva.ch', None))
self.assertEquals(self.x(u'http://\xe4\xf6\xfc\xdf.com'), (
self.assertEqual(self.x(u'http://\xe4\xf6\xfc\xdf.com'), (
'http://xn--ss-uia6e4a.com', None))
self.assertEquals(self.x(
self.assertEqual(self.x(
u'http://visegr\xe1d.com'), ('http://xn--visegrd-mwa.com', None))
self.assertEquals(self.x(u'http://h\xe1zipatika.com'), (
self.assertEqual(self.x(u'http://h\xe1zipatika.com'), (
'http://xn--hzipatika-01a.com', None))
self.assertEquals(self.x(u'http://www.\xe7ukurova.com'), (
self.assertEqual(self.x(u'http://www.\xe7ukurova.com'), (
'http://www.xn--ukurova-txa.com', None))
self.assertEquals(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None))
self.assertEquals(self.x(u'google.ca.'), ('http://google.ca.', None))
self.assertEqual(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None))
self.assertEqual(self.x(u'google.ca.'), ('http://google.ca.', None))
self.assertEquals(
self.assertEqual(
self.y(u'https://google.ca'), ('https://google.ca', None))
self.assertEquals(self.y(
self.assertEqual(self.y(
u'https://\u4e2d\u4fd4.com'), ('https://xn--fiq13b.com', None))
self.assertEquals(self.z(u'google.ca'), ('google.ca', None))
self.assertEqual(self.z(u'google.ca'), ('google.ca', None))
def testInvalidUrls(self):
self.assertEquals(
self.assertEqual(
self.x(u'://ABC.com'), (u'://ABC.com', 'Enter a valid URL'))
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.dne'), (
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.dne'), (
u'http://\u4e2d\u4fd4.dne', 'Enter a valid URL'))
self.assertEquals(self.x(u'https://google.dne'), (
self.assertEqual(self.x(u'https://google.dne'), (
u'https://google.dne', 'Enter a valid URL'))
self.assertEquals(self.x(u'https://google..ca'), (
self.assertEqual(self.x(u'https://google..ca'), (
u'https://google..ca', 'Enter a valid URL'))
self.assertEquals(
self.assertEqual(
self.x(u'google..ca'), (u'google..ca', 'Enter a valid URL'))
self.assertEquals(self.x(u'http://' + u'\u4e2d' * 1000 + u'.com'), (
self.assertEqual(self.x(u'http://' + u'\u4e2d' * 1000 + u'.com'), (
u'http://' + u'\u4e2d' * 1000 + u'.com', 'Enter a valid URL'))
self.assertEquals(self.x(u'http://google.com#fragment_\u4e86'), (
self.assertEqual(self.x(u'http://google.com#fragment_\u4e86'), (
u'http://google.com#fragment_\u4e86', 'Enter a valid URL'))
self.assertEquals(self.x(u'http\u4e86://google.com'), (
self.assertEqual(self.x(u'http\u4e86://google.com'), (
u'http\u4e86://google.com', 'Enter a valid URL'))
self.assertEquals(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (
self.assertEqual(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (
u'http\u4e86://google.com#fragment_\u4e86', 'Enter a valid URL'))
self.assertEquals(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (
self.assertEqual(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (
u'http://\u4e2d\u4fd4.com/\u4e86', 'Enter a valid URL'))
#self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'Enter a valid URL'))
#self.assertEqual(self.y(u'google.ca'), (u'google.ca', 'Enter a valid URL'))
self.assertEquals(self.z(u'invalid.domain..com'), (
self.assertEqual(self.z(u'invalid.domain..com'), (
u'invalid.domain..com', 'Enter a valid URL'))
self.assertEquals(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (
self.assertEqual(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (
u'invalid.\u4e2d\u4fd4.blargg', 'Enter a valid URL'))
# ##############################################################################
+47 -2
View File
@@ -95,14 +95,14 @@ class TestRouter(unittest.TestCase):
""" Test router syntax error """
level = logger.getEffectiveLevel()
logger.setLevel(logging.CRITICAL) # disable logging temporarily
self.assertRaises(SyntaxError, load, data='x:y')
self.assertRaises(SyntaxError, load, data='x::y')
self.assertRaises(
SyntaxError, load, rdict=dict(BASE=dict(badkey="value")))
self.assertRaises(SyntaxError, load, rdict=dict(
BASE=dict(), app=dict(default_application="name")))
self.myassertRaisesRegex(SyntaxError, "invalid syntax",
load, data='x:y')
load, data='x::y')
self.myassertRaisesRegex(SyntaxError, "unknown key",
load, rdict=dict(BASE=dict(badkey="value")))
self.myassertRaisesRegex(SyntaxError, "BASE-only key",
@@ -873,6 +873,27 @@ class TestRouter(unittest.TestCase):
self.assertEqual(str(URL(a='app2', c='default', f='index',
args=['ctr'])), "/app2/index/ctr")
# outbound - with extensions
self.assertEqual(
str(URL(a='app', c='default', f='index.json')), "/index.json")
self.assertEqual(
str(URL(a='app', c='default', f='index.json', args=['arg1'])), "/index.json/arg1")
self.assertEqual(str(
URL(a='app', c='default', f='user.json')), "/user.json")
self.assertEqual(str(
URL(a='app', c='default', f='user.json', args=['arg1'])), "/user.json/arg1")
self.assertEqual(str(URL(
a='app', c='default', f='user.json', args=['index'])), "/user.json/index")
self.assertEqual(str(
URL(a='app', c='default', f='index.json', args=['ctr'])), "/index.json/ctr")
self.assertEqual(
str(URL(a='app', c='ctr', f='ctrf1.json', args=['arg'])), "/ctr/ctrf1.json/arg")
self.assertEqual(str(URL(
a='app2', c='default', f='index.json', args=['arg1'])), "/app2/index.json/arg1")
self.assertEqual(str(URL(
a='app2', c='ctr', f='index.json', args=['arg1'])), "/app2/ctr/index.json/arg1")
# inbound
self.assertEqual(
filter_url('http://d.com/arg'), "/app/default/index ['arg']")
@@ -896,6 +917,30 @@ class TestRouter(unittest.TestCase):
self.assertEqual(
filter_url('http://d.com/app2/ctr/arg'), "/app2/ctr/arg")
# inbound - with extensions
self.assertEqual(
filter_url('http://d.com/index.json'), "/app/default/index.json")
self.assertEqual(filter_url('http://d.com/user.json'), "/app/default/user.json")
self.assertEqual(
filter_url('http://d.com/user.json/arg'), "/app/default/user.json ['arg']")
self.assertEqual(
filter_url('http://d.com/user.json/index'), "/app/default/user.json ['index']")
self.assertEqual(
filter_url('http://d.com/index.json/ctr'), "/app/default/index.json ['ctr']")
self.assertEqual(
filter_url('http://d.com/ctr/ctrf1.json/arg'), "/app/ctr/ctrf1.json ['arg']")
self.assertEqual(filter_url(
'http://d.com/app2/index.json/arg'), "/app2/default/index.json ['arg']")
self.assertEqual(
filter_url('http://d.com/app2/user.json'), "/app2/default/user.json")
self.assertEqual(filter_url(
'http://d.com/app2/user.json/arg'), "/app2/default/user.json ['arg']")
self.assertEqual(filter_url(
'http://d.com/app2/ctr/index.json/arg'), "/app2/ctr/index.json ['arg']")
self.assertEqual(
filter_url('http://d.com/app2/ctr/arg'), "/app2/ctr/arg")
def test_router_functions2(self):
'''
Test more functions=[something]
+28 -15
View File
@@ -235,6 +235,11 @@ class TestValidators(unittest.TestCase):
self.assertEqual(sorted(rtn), [('%d' % george_id, 'george'), ('%d' % costanza_id, 'costanza')])
rtn = IS_IN_DB(db, db.person.id, db.person.name, error_message='oops', sort=True).options(zero=True)
self.assertEqual(rtn, [('', ''), ('%d' % costanza_id, 'costanza'), ('%d' % george_id, 'george')])
# Test None
rtn = IS_IN_DB(db, 'person.id', '%(name)s', error_message='oops')(None)
self.assertEqual(rtn, (None, 'oops'))
rtn = IS_IN_DB(db, 'person.name', '%(name)s', error_message='oops')(None)
self.assertEqual(rtn, (None, 'oops'))
# Test using the set it made for options
vldtr = IS_IN_DB(db, 'person.name', '%(name)s', error_message='oops')
vldtr.options()
@@ -434,21 +439,21 @@ class TestValidators(unittest.TestCase):
rtn = IS_NOT_EMPTY()('x')
self.assertEqual(rtn, ('x', None))
rtn = IS_NOT_EMPTY()(' x ')
self.assertEqual(rtn, ('x', None))
self.assertEqual(rtn, (' x ', None))
rtn = IS_NOT_EMPTY()(None)
self.assertEqual(rtn, (None, 'Enter a value'))
rtn = IS_NOT_EMPTY()('')
self.assertEqual(rtn, ('', 'Enter a value'))
rtn = IS_NOT_EMPTY()(' ')
self.assertEqual(rtn, ('', 'Enter a value'))
self.assertEqual(rtn, (' ', 'Enter a value'))
rtn = IS_NOT_EMPTY()(' \n\t')
self.assertEqual(rtn, ('', 'Enter a value'))
self.assertEqual(rtn, (' \n\t', 'Enter a value'))
rtn = IS_NOT_EMPTY()([])
self.assertEqual(rtn, ([], 'Enter a value'))
rtn = IS_NOT_EMPTY(empty_regex='def')('def')
self.assertEqual(rtn, ('', 'Enter a value'))
self.assertEqual(rtn, ('def', 'Enter a value'))
rtn = IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
self.assertEqual(rtn, ('', 'Enter a value'))
self.assertEqual(rtn, ('deg', 'Enter a value'))
rtn = IS_NOT_EMPTY(empty_regex='def')('abc')
self.assertEqual(rtn, ('abc', None))
@@ -531,6 +536,11 @@ class TestValidators(unittest.TestCase):
rtn = IS_EMAIL(error_message='oops')(42)
self.assertEqual(rtn, (42, 'oops'))
# test for Internationalized Domain Names, see https://docs.python.org/2/library/codecs.html#module-encodings.idna
rtn = IS_EMAIL()('web2py@Alliancefrançaise.nu')
self.assertEqual(rtn, ('web2py@Alliancefrançaise.nu', None))
def test_IS_LIST_OF_EMAILS(self):
emails = ['localguy@localhost', '_Yosemite.Sam@example.com']
rtn = IS_LIST_OF_EMAILS()(','.join(emails))
@@ -702,15 +712,19 @@ class TestValidators(unittest.TestCase):
def test_IS_LOWER(self):
rtn = IS_LOWER()('ABC')
self.assertEqual(rtn, ('abc', None))
rtn = IS_LOWER()(b'ABC')
self.assertEqual(rtn, (b'abc', None))
rtn = IS_LOWER()('Ñ')
self.assertEqual(rtn, (b'\xc3\xb1', None))
self.assertEqual(rtn, ('ñ', None))
def test_IS_UPPER(self):
rtn = IS_UPPER()('abc')
self.assertEqual(rtn, ('ABC', None))
rtn = IS_UPPER()(b'abc')
self.assertEqual(rtn, (b'ABC', None))
rtn = IS_UPPER()('ñ')
self.assertEqual(rtn, (b'\xc3\x91', None))
self.assertEqual(rtn, ('Ñ', None))
def test_IS_SLUG(self):
rtn = IS_SLUG()('abc123')
@@ -780,7 +794,7 @@ class TestValidators(unittest.TestCase):
rtn = IS_EMPTY_OR(IS_EMAIL())('abc')
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
rtn = IS_EMPTY_OR(IS_EMAIL())(' abc ')
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
self.assertEqual(rtn, (' abc ', 'Enter a valid email address'))
rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options(zero=False)
self.assertEqual(rtn, [('', ''), ('id1', 'first label'), ('id2', 'second label')])
rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options()
@@ -843,7 +857,7 @@ class TestValidators(unittest.TestCase):
'|'.join(['Minimum length is 8',
'Maximum length is 4',
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
'Must include at least 1 upper case',
'Must include at least 1 uppercase',
'Must include at least 1 number']))
)
rtn = IS_STRONG(es=True)('abcde')
@@ -851,7 +865,7 @@ class TestValidators(unittest.TestCase):
('abcde',
'|'.join(['Minimum length is 8',
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
'Must include at least 1 upper case',
'Must include at least 1 uppercase',
'Must include at least 1 number']))
)
rtn = IS_STRONG(upper=0, lower=0, number=0, es=True)('Abcde1')
@@ -859,8 +873,8 @@ class TestValidators(unittest.TestCase):
('Abcde1',
'|'.join(['Minimum length is 8',
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
'May not include any upper case letters',
'May not include any lower case letters',
'May not include any uppercase letters',
'May not include any lowercase letters',
'May not include any numbers']))
)
@@ -1029,12 +1043,11 @@ this is the content of the fake file
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', None))
# TODO:
# with py3.ipaddress '2001::126c:8ffa:fe22:b3af' is considered private
# with py2.ipaddress '2001::126c:8ffa:fe22:b3af' is considered private
# with gluon.contrib.ipaddr(both current and trunk) is not considered private
# rtn = IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
# self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', None))
rtn = IS_IPV6(is_routeable=False)('2001::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', None))
rtn = IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
rtn = IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af')
+1 -1
View File
@@ -110,7 +110,7 @@ class TestWeb(LiveTest):
self.assertTrue('Welcome Homer' in client.text)
client = WebClient('http://127.0.0.1:8000/admin/default/')
client.post('index', data=dict(password='hello'))
client.post('index', data=dict(password='testpass'))
client.get('site')
client.get('design/welcome')
+356 -892
View File
File diff suppressed because it is too large Load Diff
+20 -18
View File
@@ -17,6 +17,7 @@ import random
import inspect
import time
import os
import sys
import re
import logging
import socket
@@ -155,12 +156,12 @@ def get_digest(value):
raise ValueError("Invalid digest algorithm: %s" % value)
DIGEST_ALG_BY_SIZE = {
128 / 4: 'md5',
160 / 4: 'sha1',
224 / 4: 'sha224',
256 / 4: 'sha256',
384 / 4: 'sha384',
512 / 4: 'sha512',
128 // 4: 'md5',
160 // 4: 'sha1',
224 // 4: 'sha224',
256 // 4: 'sha256',
384 // 4: 'sha384',
512 // 4: 'sha512',
}
@@ -299,19 +300,20 @@ def initialize_urandom():
try:
os.urandom(1)
have_urandom = True
try:
# try to add process-specific entropy
frandom = open('/dev/urandom', 'wb')
if sys.platform != 'win32':
try:
if PY2:
frandom.write(''.join(chr(t) for t in ctokens))
else:
frandom.write(bytes([]).join(bytes([t]) for t in ctokens))
finally:
frandom.close()
except IOError:
# works anyway
pass
# try to add process-specific entropy
frandom = open('/dev/urandom', 'wb')
try:
if PY2:
frandom.write(''.join(chr(t) for t in ctokens))
else:
frandom.write(bytes([]).join(bytes([t]) for t in ctokens))
finally:
frandom.close()
except IOError:
# works anyway
pass
except NotImplementedError:
have_urandom = False
logger.warning(
+143 -123
View File
@@ -10,7 +10,6 @@
Validators
-----------
"""
import os
import re
import datetime
@@ -21,7 +20,9 @@ import urllib
import struct
import decimal
import unicodedata
from gluon._compat import StringIO, long, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, to_unicode, to_native
from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, \
to_unicode, to_native, string_types, urlparse
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
from pydal.objects import Field, FieldVirtual, FieldMethod
from functools import reduce
@@ -195,7 +196,7 @@ class IS_MATCH(Validator):
self.is_unicode = is_unicode or (not(PY2))
def __call__(self, value):
if not(PY2): # PY3 convert bytes to unicode
if not(PY2): # PY3 convert bytes to unicode
value = to_unicode(value)
if self.is_unicode or not(PY2):
@@ -270,7 +271,7 @@ class IS_EXPR(Validator):
return (value, self.expression(value))
# for backward compatibility
self.environment.update(value=value)
exec ('__ret__=' + self.expression, self.environment)
exec('__ret__=' + self.expression, self.environment)
if self.environment['__ret__']:
return (value, None)
return (value, translate(self.error_message))
@@ -452,10 +453,10 @@ class IS_IN_SET(Validator):
if not self.labels:
items = [(k, k) for (i, k) in enumerate(self.theset)]
else:
items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
items = [(k, list(self.labels)[i]) for (i, k) in enumerate(self.theset)]
if self.sort:
items.sort(key=lambda o: str(o[1]).upper())
if zero and not self.zero is None and not self.multiple:
if zero and self.zero is not None and not self.multiple:
items.insert(0, ('', self.zero))
return items
@@ -659,7 +660,7 @@ class IS_IN_DB(Validator):
return (values, None)
else:
if field.type in ('id', 'integer'):
if isinstance(value, (int, long)) or value.isdigit():
if isinstance(value, (int, long)) or (isinstance(value, string_types) and value.isdigit()):
value = int(value)
elif self.auto_add:
value = self.maybe_add(table, self.fieldnames[0], value)
@@ -823,7 +824,7 @@ class IS_INT_IN_RANGE(Validator):
def str2dec(number):
s = str(number)
if not '.' in s:
if '.' not in s:
s += '.00'
else:
s += '0' * (2 - len(s.split('.')[1]))
@@ -989,14 +990,15 @@ class IS_DECIMAL_IN_RANGE(Validator):
def is_empty(value, empty_regex=None):
_value = value
"""test empty field"""
if isinstance(value, (str, unicodeT)):
value = value.strip()
if empty_regex is not None and empty_regex.match(value):
value = ''
if value is None or value == '' or value == []:
return (value, True)
return (value, False)
return (_value, True)
return (_value, False)
class IS_NOT_EMPTY(Validator):
@@ -1156,15 +1158,16 @@ class IS_EMAIL(Validator):
"""
regex = re.compile('''
body_regex = re.compile('''
^(?!\.) # name may not begin with a dot
(
[-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
|
(?<!\.)\. # single dots only
)+
(?<!\.) # name may not end with a dot
@
(?<!\.)$ # name may not end with a dot
''', re.VERBOSE | re.IGNORECASE)
domain_regex = re.compile('''
(
localhost
|
@@ -1196,14 +1199,27 @@ class IS_EMAIL(Validator):
self.error_message = error_message
def __call__(self, value):
if not(isinstance(value, (basestring, unicodeT))) or not value or '@' not in value:
return (value, translate(self.error_message))
body, domain = value.rsplit('@', 1)
try:
match = self.regex.match(value)
except TypeError:
match_body = self.body_regex.match(body)
match_domain = self.domain_regex.match(domain)
if not match_domain:
# check for Internationalized Domain Names
# see https://docs.python.org/2/library/codecs.html#module-encodings.idna
domain_encoded = to_unicode(domain).encode('idna').decode('ascii')
match_domain = self.domain_regex.match(domain_encoded)
match = (match_body is not None) and (match_domain is not None)
except (TypeError, UnicodeError):
# Value may not be a string where we can look for matches.
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
match = None
if match:
domain = value.split('@')[1]
if (not self.banned or not self.banned.match(domain)) \
and (not self.forced or self.forced.match(domain)):
return (value, None)
@@ -1232,7 +1248,7 @@ class IS_LIST_OF_EMAILS(object):
f = IS_EMAIL()
for email in self.split_emails.findall(value):
error = f(email)[1]
if error and not email in bad_emails:
if error and email not in bad_emails:
bad_emails.append(email)
if not bad_emails:
return (value, None)
@@ -1372,19 +1388,6 @@ unofficial_url_schemes = [
all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
http_schemes = [None, 'http', 'https']
# This regex comes from RFC 2396, Appendix B. It's used to split a URL into
# its component parts
# Here are the regex groups that it extracts:
# scheme = group(2)
# authority = group(4)
# path = group(5)
# query = group(7)
# fragment = group(9)
url_split_regex = \
re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?')
# Defined in RFC 3490, Section 3.1, Requirement #1
# Use this regex to split the authority component of a unicode URL into
# its component labels
@@ -1454,18 +1457,15 @@ def unicode_to_ascii_authority(authority):
# We use the ToASCII operation because we are about to put the authority
# into an IDN-unaware slot
asciiLabels = []
try:
import encodings.idna
for label in labels:
if label:
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
else:
# encodings.idna.ToASCII does not accept an empty string, but
# it is necessary for us to allow for empty labels so that we
# don't modify the URL
asciiLabels.append('')
except:
asciiLabels = [str(label) for label in labels]
import encodings.idna
for label in labels:
if label:
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
else:
# encodings.idna.ToASCII does not accept an empty string, but
# it is necessary for us to allow for empty labels so that we
# don't modify the URL
asciiLabels.append('')
# RFC 3490, Section 4, Step 5
return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
@@ -1504,33 +1504,39 @@ def unicode_to_ascii_url(url, prepend_scheme):
"""
# convert the authority component of the URL into an ASCII punycode string,
# but encode the rest using the regular URI character encoding
groups = url_split_regex.match(url).groups()
components = urlparse.urlparse(url)
prepended = False
# If no authority was found
if not groups[3]:
if not components.netloc:
# Try appending a scheme to see if that fixes the problem
scheme_to_prepend = prepend_scheme or 'http'
groups = url_split_regex.match(
to_unicode(scheme_to_prepend) + u'://' + url).groups()
components = urlparse.urlparse(to_unicode(scheme_to_prepend) + u'://' + url)
prepended = True
# if we still can't find the authority
if not groups[3]:
if not components.netloc:
raise Exception('No authority component found, ' +
'could not decode unicode to US-ASCII')
# We're here if we found an authority, let's rebuild the URL
scheme = groups[1]
authority = groups[3]
path = groups[4] or ''
query = groups[5] or ''
fragment = groups[7] or ''
scheme = components.scheme
authority = components.netloc
path = components.path
query = components.query
fragment = components.fragment
if prepend_scheme:
scheme = str(scheme) + '://'
else:
if prepended:
scheme = ''
return scheme + unicode_to_ascii_authority(authority) +\
escape_unicode(path) + escape_unicode(query) + str(fragment)
unparsed = urlparse.urlunparse((scheme,
unicode_to_ascii_authority(authority),
escape_unicode(path),
'',
escape_unicode(query),
str(fragment)))
if unparsed.startswith('//'):
unparsed = unparsed[2:] # Remove the // urlunparse puts in the beginning
return unparsed
class IS_GENERIC_URL(Validator):
@@ -1591,7 +1597,8 @@ class IS_GENERIC_URL(Validator):
% (self.prepend_scheme, self.allowed_schemes))
GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$")
GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$")
GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%]+$")
URL_FRAGMENT_VALID = re.compile(r"[|A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%]+$")
def __call__(self, value):
"""
@@ -1603,41 +1610,49 @@ class IS_GENERIC_URL(Validator):
prepended with prepend_scheme), and tuple[1] is either
None (success!) or the string error_message
"""
try:
# if the URL does not misuse the '%' character
if not self.GENERIC_URL.search(value):
# if the URL is only composed of valid characters
if self.GENERIC_URL_VALID.match(value):
# Then split up the URL into its components and check on
# the scheme
scheme = url_split_regex.match(value).group(2)
# Clean up the scheme before we check it
if not scheme is None:
scheme = urllib_unquote(scheme).lower()
# If the scheme really exists
if scheme in self.allowed_schemes:
# Then the URL is valid
return (value, None)
else:
# else, for the possible case of abbreviated URLs with
# ports, check to see if adding a valid scheme fixes
# the problem (but only do this if it doesn't have
# one already!)
if value.find('://') < 0 and None in self.allowed_schemes:
schemeToUse = self.prepend_scheme or 'http'
prependTest = self.__call__(
schemeToUse + '://' + value)
# if the prepend test succeeded
if prependTest[1] is None:
# if prepending in the output is enabled
if self.prepend_scheme:
return prependTest
else:
# else return the original,
# non-prepended value
return (value, None)
except:
pass
# if we dont have anything or the URL misuses the '%' character
if not value or self.GENERIC_URL.search(value):
return (value, translate(self.error_message))
if '#' in value:
url, fragment_part = value.split('#')
else:
url, fragment_part = value, ''
# if the URL is only composed of valid characters
if self.GENERIC_URL_VALID.match(url) and (not fragment_part or self.URL_FRAGMENT_VALID.match(fragment_part)):
# Then parse the URL into its components and check on
try:
components = urlparse.urlparse(urllib_unquote(value))._asdict()
except ValueError:
return (value, translate(self.error_message))
# Clean up the scheme before we check it
scheme = components['scheme']
if len(scheme) == 0:
scheme = None
else:
scheme = components['scheme'].lower()
# If the scheme doesn't really exists
if scheme not in self.allowed_schemes or not scheme and ':' in components['path']:
# for the possible case of abbreviated URLs with
# ports, check to see if adding a valid scheme fixes
# the problem (but only do this if it doesn't have
# one already!)
if '://' not in value and None in self.allowed_schemes:
schemeToUse = self.prepend_scheme or 'http'
prependTest = self.__call__(
schemeToUse + '://' + value)
# if the prepend test succeeded
if prependTest[1] is None:
# if prepending in the output is enabled
if self.prepend_scheme:
return prependTest
else:
return (value, None)
else:
return (value, None)
# else the URL is not valid
return (value, translate(self.error_message))
@@ -1904,15 +1919,14 @@ class IS_HTTP_URL(Validator):
(possible prepended with prepend_scheme), and tuple[1] is either
None (success!) or the string error_message
"""
try:
# if the URL passes generic validation
x = IS_GENERIC_URL(error_message=self.error_message,
allowed_schemes=self.allowed_schemes,
prepend_scheme=self.prepend_scheme)
if x(value)[1] is None:
componentsMatch = url_split_regex.match(value)
authority = componentsMatch.group(4)
components = urlparse.urlparse(value)
authority = components.netloc
# if there is an authority component
if authority:
# if authority is a valid IP address
@@ -1932,7 +1946,7 @@ class IS_HTTP_URL(Validator):
else:
# else this is a relative/abbreviated URL, which will parse
# into the URL's path component
path = componentsMatch.group(5)
path = components.path
# relative case: if this is a valid path (if it starts with
# a slash)
if path.startswith('/'):
@@ -1941,7 +1955,7 @@ class IS_HTTP_URL(Validator):
else:
# abbreviated case: if we haven't already, prepend a
# scheme and see if it fixes the problem
if value.find('://') < 0:
if '://' not in value and None in self.allowed_schemes:
schemeToUse = self.prepend_scheme or 'http'
prependTest = self.__call__(schemeToUse
+ '://' + value)
@@ -2108,7 +2122,6 @@ class IS_URL(Validator):
# If we are not able to convert the unicode url into a
# US-ASCII URL, then the URL is not valid
return (value, translate(self.error_message))
methodResult = subMethod(asciiValue)
# if the validation of the US-ASCII version of the value failed
if not methodResult[1] is None:
@@ -2470,7 +2483,7 @@ class IS_LIST_OF(Validator):
class IS_LOWER(Validator):
"""
Converts to lower case::
Converts to lowercase::
>>> IS_LOWER()('ABC')
('abc', None)
@@ -2480,12 +2493,18 @@ class IS_LOWER(Validator):
"""
def __call__(self, value):
return (to_bytes(to_unicode(value).lower()), None)
cast_back = lambda x: x
if isinstance(value, str):
cast_back = to_native
elif isinstance(value, bytes):
cast_back = to_bytes
value = to_unicode(value).lower()
return (cast_back(value), None)
class IS_UPPER(Validator):
"""
Converts to upper case::
Converts to uppercase::
>>> IS_UPPER()('abc')
('ABC', None)
@@ -2495,7 +2514,13 @@ class IS_UPPER(Validator):
"""
def __call__(self, value):
return (to_bytes(to_unicode(value).upper()), None)
cast_back = lambda x: x
if isinstance(value, str):
cast_back = to_native
elif isinstance(value, bytes):
cast_back = to_bytes
value = to_unicode(value).upper()
return (cast_back(value), None)
def urlify(s, maxlen=80, keep_underscores=False):
@@ -2602,7 +2627,7 @@ class ANY_OF(Validator):
def __call__(self, value):
for validator in self.subs:
value, error = validator(value)
if error == None:
if error is None:
break
return value, error
@@ -2742,7 +2767,7 @@ class LazyCrypt(object):
else:
digest_alg, key = self.crypt.digest_alg, ''
if self.crypt.salt:
if self.crypt.salt == True:
if self.crypt.salt is True:
salt = str(web2py_uuid()).replace('-', '')[-16:]
else:
salt = self.crypt.salt
@@ -2827,7 +2852,7 @@ class CRYPT(object):
Supports standard algorithms
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
... print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
... print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
md5$...$...
sha1$...$...
sha256$...$...
@@ -2839,13 +2864,13 @@ class CRYPT(object):
Supports for pbkdf2
>>> alg = 'pbkdf2(1000,20,sha512)'
>>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
>>> print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
pbkdf2(1000,20,sha512)$...$...
An optional hmac_key can be specified and it is used as salt prefix
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
>>> print a
>>> print(a)
md5$...$...
Even if the algorithm changes the hash can still be validated
@@ -3025,22 +3050,22 @@ class IS_STRONG(object):
all_upper = re.findall("[A-Z]", value)
if self.upper > 0:
if not len(all_upper) >= self.upper:
failures.append(translate("Must include at least %s upper case")
failures.append(translate("Must include at least %s uppercase")
% str(self.upper))
else:
if len(all_upper) > 0:
failures.append(
translate("May not include any upper case letters"))
translate("May not include any uppercase letters"))
if isinstance(self.lower, int):
all_lower = re.findall("[a-z]", value)
if self.lower > 0:
if not len(all_lower) >= self.lower:
failures.append(translate("Must include at least %s lower case")
failures.append(translate("Must include at least %s lowercase")
% str(self.lower))
else:
if len(all_lower) > 0:
failures.append(
translate("May not include any lower case letters"))
translate("May not include any lowercase letters"))
if isinstance(self.number, int):
all_number = re.findall("[0-9]", value)
if self.number > 0:
@@ -3505,10 +3530,7 @@ class IS_IPV6(Validator):
self.error_message = error_message
def __call__(self, value):
try:
import ipaddress
except ImportError:
from gluon.contrib import ipaddr as ipaddress
from gluon._compat import ipaddress
try:
ip = ipaddress.IPv6Address(to_unicode(value))
@@ -3732,12 +3754,10 @@ class IS_IPADDRESS(Validator):
self.error_message = error_message
def __call__(self, value):
try:
from ipaddress import ip_address as IPAddress
from ipaddress import IPv6Address, IPv4Address
except ImportError:
from gluon.contrib.ipaddr import (IPAddress, IPv4Address,
IPv6Address)
from gluon._compat import ipaddress
IPAddress = ipaddress.ip_address
IPv6Address = ipaddress.IPv6Address
IPv4Address = ipaddress.IPv4Address
try:
ip = IPAddress(to_unicode(value))
+51 -40
View File
@@ -13,7 +13,7 @@ from __future__ import print_function
import datetime
import sys
from gluon._compat import StringIO, thread, xrange
from gluon._compat import StringIO, thread, xrange, PY2
import time
import threading
import os
@@ -134,24 +134,29 @@ class web2pyDialog(object):
def __init__(self, root, options):
""" web2pyDialog constructor """
import Tkinter
import tkMessageBox
if PY2:
import Tkinter as tkinter
import tkMessageBox as messagebox
else:
import tkinter
from tkinter import messagebox
bg_color = 'white'
root.withdraw()
self.root = Tkinter.Toplevel(root, bg=bg_color)
self.root = tkinter.Toplevel(root, bg=bg_color)
self.root.resizable(0, 0)
self.root.title(ProgramName)
self.options = options
self.scheduler_processes = {}
self.menu = Tkinter.Menu(self.root)
servermenu = Tkinter.Menu(self.menu, tearoff=0)
self.menu = tkinter.Menu(self.root)
servermenu = tkinter.Menu(self.menu, tearoff=0)
httplog = os.path.join(self.options.folder, self.options.log_filename)
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
if os.path.exists(iconphoto):
img = Tkinter.PhotoImage(file=iconphoto)
img = tkinter.PhotoImage(file=iconphoto)
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
# Building the Menu
item = lambda: start_browser(httplog)
@@ -163,16 +168,16 @@ class web2pyDialog(object):
self.menu.add_cascade(label='Server', menu=servermenu)
self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0)
self.pagesmenu = tkinter.Menu(self.menu, tearoff=0)
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
#scheduler menu
self.schedmenu = Tkinter.Menu(self.menu, tearoff=0)
self.schedmenu = tkinter.Menu(self.menu, tearoff=0)
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
#start and register schedulers from options
self.update_schedulers(start=True)
helpmenu = Tkinter.Menu(self.menu, tearoff=0)
helpmenu = tkinter.Menu(self.menu, tearoff=0)
# Home Page
item = lambda: start_browser('http://www.web2py.com/')
@@ -180,7 +185,7 @@ class web2pyDialog(object):
command=item)
# About
item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo)
item = lambda: messagebox.showinfo('About web2py', ProgramInfo)
helpmenu.add_command(label='About',
command=item)
@@ -194,10 +199,10 @@ class web2pyDialog(object):
else:
self.root.protocol('WM_DELETE_WINDOW', self.quit)
sticky = Tkinter.NW
sticky = tkinter.NW
# Prepare the logo area
self.logoarea = Tkinter.Canvas(self.root,
self.logoarea = tkinter.Canvas(self.root,
background=bg_color,
width=300,
height=300)
@@ -206,22 +211,22 @@ class web2pyDialog(object):
logo = os.path.join('extras', 'icons', 'splashlogo.gif')
if os.path.exists(logo):
img = Tkinter.PhotoImage(file=logo)
pnl = Tkinter.Label(self.logoarea, image=img, background=bg_color, bd=0)
img = tkinter.PhotoImage(file=logo)
pnl = tkinter.Label(self.logoarea, image=img, background=bg_color, bd=0)
pnl.pack(side='top', fill='both', expand='yes')
# Prevent garbage collection of img
pnl.image = img
# Prepare the banner area
self.bannerarea = Tkinter.Canvas(self.root,
self.bannerarea = tkinter.Canvas(self.root,
bg=bg_color,
width=300,
height=300)
self.bannerarea.grid(row=1, column=1, columnspan=2, sticky=sticky)
Tkinter.Label(self.bannerarea, anchor=Tkinter.N,
tkinter.Label(self.bannerarea, anchor=tkinter.N,
text=str(ProgramVersion + "\n" + ProgramAuthor),
font=('Helvetica', 11), justify=Tkinter.CENTER,
font=('Helvetica', 11), justify=tkinter.CENTER,
foreground='#195866', background=bg_color,
height=3).pack(side='top',
fill='both',
@@ -230,24 +235,24 @@ class web2pyDialog(object):
self.bannerarea.after(1000, self.update_canvas)
# IP
Tkinter.Label(self.root,
tkinter.Label(self.root,
text='Server IP:', bg=bg_color,
justify=Tkinter.RIGHT).grid(row=4,
justify=tkinter.RIGHT).grid(row=4,
column=1,
sticky=sticky)
self.ips = {}
self.selected_ip = Tkinter.StringVar()
self.selected_ip = tkinter.StringVar()
row = 4
ips = [('127.0.0.1', 'Local (IPv4)')] + \
([('::1', 'Local (IPv6)')] if socket.has_ipv6 else []) + \
[(ip, 'Public') for ip in options.ips] + \
[('0.0.0.0', 'Public')]
for ip, legend in ips:
self.ips[ip] = Tkinter.Radiobutton(
self.ips[ip] = tkinter.Radiobutton(
self.root, bg=bg_color, highlightthickness=0,
selectcolor='light grey', width=30,
anchor=Tkinter.W, text='%s (%s)' % (legend, ip),
justify=Tkinter.LEFT,
anchor=tkinter.W, text='%s (%s)' % (legend, ip),
justify=tkinter.LEFT,
variable=self.selected_ip, value=ip)
self.ips[ip].grid(row=row, column=2, sticky=sticky)
if row == 4:
@@ -256,30 +261,30 @@ class web2pyDialog(object):
shift = row
# Port
Tkinter.Label(self.root,
tkinter.Label(self.root,
text='Server Port:', bg=bg_color,
justify=Tkinter.RIGHT).grid(row=shift,
justify=tkinter.RIGHT).grid(row=shift,
column=1, pady=10,
sticky=sticky)
self.port_number = Tkinter.Entry(self.root)
self.port_number.insert(Tkinter.END, self.options.port)
self.port_number = tkinter.Entry(self.root)
self.port_number.insert(tkinter.END, self.options.port)
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
# Password
Tkinter.Label(self.root,
tkinter.Label(self.root,
text='Choose Password:', bg=bg_color,
justify=Tkinter.RIGHT).grid(row=shift + 1,
justify=tkinter.RIGHT).grid(row=shift + 1,
column=1,
sticky=sticky)
self.password = Tkinter.Entry(self.root, show='*')
self.password = tkinter.Entry(self.root, show='*')
self.password.bind('<Return>', lambda e: self.start())
self.password.focus_force()
self.password.grid(row=shift + 1, column=2, sticky=sticky)
# Prepare the canvas
self.canvas = Tkinter.Canvas(self.root,
self.canvas = tkinter.Canvas(self.root,
width=400,
height=100,
bg='black')
@@ -288,19 +293,19 @@ class web2pyDialog(object):
self.canvas.after(1000, self.update_canvas)
# Prepare the frame
frame = Tkinter.Frame(self.root)
frame = tkinter.Frame(self.root)
frame.grid(row=shift + 3, column=1, columnspan=2, pady=5,
sticky=sticky)
# Start button
self.button_start = Tkinter.Button(frame,
self.button_start = tkinter.Button(frame,
text='start server',
command=self.start)
self.button_start.grid(row=0, column=0, sticky=sticky)
# Stop button
self.button_stop = Tkinter.Button(frame,
self.button_stop = tkinter.Button(frame,
text='stop server',
command=self.stop)
@@ -454,9 +459,12 @@ class web2pyDialog(object):
def error(self, message):
""" Shows error message """
import tkMessageBox
tkMessageBox.showerror('web2py start server', message)
if PY2:
import tkMessageBox as messagebox
else:
from tkinter import messagebox
messagebox.showerror('web2py start server', message)
def start(self):
""" Starts web2py server """
@@ -1191,10 +1199,13 @@ def start(cron=True):
if not options.nogui and options.password == '<ask>':
try:
import Tkinter
if PY2:
import Tkinter as tkinter
else:
import tkinter
havetk = True
try:
root = Tkinter.Tk()
root = tkinter.Tk()
except:
pass
except (ImportError, OSError):
+2 -1
View File
@@ -15,6 +15,7 @@ except ImportError:
import readline
try:
from gluon import DAL
from gluon.fileutils import open_file
except ImportError as err:
print('gluon path not found')
@@ -531,7 +532,7 @@ class setCopyDB():
self.db.export_to_csv_file(open('tmp.sql', 'wb'))
print 'importing data...'
other_db.import_from_csv_file(open('tmp.sql', 'rb'))
other_db.import_from_csv_file(open_file('tmp.sql', 'rb'))
other_db.commit()
print 'done!'
print 'Attention: do not run this program again or you end up with duplicate records'
+13 -7
View File
@@ -18,7 +18,7 @@ legacy_db(legacy_db.mytable.id>0).select()
If the script crashes this is might be due to that fact that the data_type_map dictionary below is incomplete.
Please complete it, improve it and continue.
Created by Falko Krause, minor modifications by Massimo Di Pierro and Ron McOuat
Created by Falko Krause, minor modifications by Massimo Di Pierro, Ron McOuat and Marvi Benedet
'''
import subprocess
import re
@@ -52,10 +52,11 @@ data_type_map = dict(
)
def mysql(database_name, username, password):
def mysql(database_name, username, password, host):
p = subprocess.Popen(['mysql',
'--user=%s' % username,
'--password=%s' % password,
'--host=%s' % host,
'--execute=show tables;',
database_name],
stdin=subprocess.PIPE,
@@ -64,14 +65,15 @@ def mysql(database_name, username, password):
sql_showtables, stderr = p.communicate()
tables = [re.sub(
'\|\s+([^\|*])\s+.*', '\1', x) for x in sql_showtables.split()[1:]]
connection_string = "legacy_db = DAL('mysql://%s:%s@localhost/%s')" % (
username, password, database_name)
connection_string = "legacy_db = DAL('mysql://%s:%s@%s/%s')" % (
username, password, host, database_name)
legacy_db_table_web2py_code = []
for table_name in tables:
#get the sql create statement
p = subprocess.Popen(['mysqldump',
'--user=%s' % username,
'--password=%s' % password,
'--host=%s' % host,
'--skip-add-drop-table',
'--no-data', database_name,
table_name], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -106,9 +108,13 @@ def mysql(database_name, username, password):
legacy_db_table_web2py_code)
return legacy_db_web2py_code
regex = re.compile('(.*?):(.*?)@(.*)')
regex = re.compile('(.*):(.*)@((.*)/)?(.*)')
if len(sys.argv) < 2 or not regex.match(sys.argv[1]):
print 'USAGE:\n\n extract_mysql_models.py username:password@data_basename\n\n'
print 'USAGE:\n\n extract_mysql_models.py username:password@[host/]data_basename\n\n'
else:
m = regex.match(sys.argv[1])
print mysql(m.group(3), m.group(1), m.group(2))
username = m.group(1)
password = m.group(2)
host = m.group(4) or 'localhost'
db_name = m.group(5)
print mysql(database_name = db_name, username = username, password = password, host = host)
@@ -216,6 +216,7 @@ fi
/etc/init.d/nginx start
systemctl start emperor.uwsgi.service
systemctl enable emperor.uwsgi.service
echo <<EOF
you can stop uwsgi and nginx with