Compare commits

..

432 Commits

Author SHA1 Message Date
mdipierro da2f027501 testing revert in pydal 2018-02-24 19:25:30 -06:00
mdipierro 7877220057 testing ac8dffff06a001c4f7f3ac970c3bea3077a4308c 2018-02-24 18:13:04 -06:00
mdipierro 0c465b27a4 testing 13ad4087a74fd5f7af8060ad9a58e7f74e69edf0 2018-02-24 18:05:03 -06:00
mdipierro b26184b010 testing 53a5cb2c2c9f6087984bf727e0efcaf297150e6d 2018-02-24 17:58:45 -06:00
mdipierro d83d3535be testing ccc4d4c7f8e9bfb285597d266489e169ecadbbfa 2018-02-24 17:50:10 -06:00
mdipierro 82a4d0b600 testing 2018-02-24 17:42:22 -06:00
mdipierro a037537497 reverted to stable pydal 2018-02-24 13:35:45 -06:00
mdipierro fa99620240 cannot promise forms will not changes, removed form tests 2018-02-24 13:23:09 -06:00
mdipierro ee19a48521 Merge pull request #1858 from daniellibonati/dlibonati-bootstrap4_inline_fix
Added row class to form-group so form becomes inline in Bootstrap v4
2018-02-24 12:58:47 -06:00
mdipierro 698ca8bbb8 font-awesome 2018-02-24 12:33:19 -06:00
mdipierro 9c6df11459 font-awesome 2018-02-24 12:31:33 -06:00
mdipierro ee4eb3f15a Merge branch 'master' of github.com:web2py/web2py 2018-02-24 11:56:57 -06:00
mdipierro a0cdad2ddf Merge pull request #1870 from ilvalle/fix_rocket_py3_bytes
fix PY3 rocket with bytes stream
2018-02-24 11:02:42 -06:00
mdipierro cc8abd0a0f Merge pull request #1869 from dartg/master
Replace the guppy library with Pympler
2018-02-24 11:02:01 -06:00
mdipierro f44d617445 Merge pull request #1860 from blackthorne/master
patched flash function to prevent XSS
2018-02-24 11:01:04 -06:00
mdipierro c17d6bb5d1 Merge pull request #1855 from ilvalle/fix_reload_py3
fix missing reload py3
2018-02-24 10:59:03 -06:00
mdipierro c9999499bb Merge pull request #1854 from liuyigh/patch-1
Fix layout.html "Lost Password" link
2018-02-24 10:58:35 -06:00
mdipierro 61b5343f66 Merge pull request #1852 from ilvalle/fix_languages_py3
fix py3 languages decode
2018-02-24 10:57:17 -06:00
ilvalle 164a271ca8 fix PY3 rocket with bytes stream. ref web2py/pydal#516 2018-02-23 19:43:34 +01:00
dartg 419dc7b5fc Replace the guppy library with Pympler 2018-02-20 02:34:21 -03:00
ilvalle fa31a6b61b Added test for T used in HTML tags, fix language encode(), close #1856 2018-02-12 21:13:19 +01:00
Francisco Ribeiro 7aafd05cbb patched flash function to prevent XSS 2018-02-12 11:36:34 +00:00
Daniel Libonati 79ba8cb6e4 Added row class to form-group so form becomes inline in Bootstrap v4 2018-02-10 22:07:01 -03:00
ilvalle c3355b7457 fix missing reload py3, close #1760 2018-02-09 20:56:16 +01:00
Yi Liu 546440ece0 Fix layout.html "Lost Password" link 2018-02-09 11:48:00 -08:00
ilvalle 44e27bf65f fix py3 languages decode added tests. close #1848, thanks @bjquinn 2018-02-09 20:46:00 +01:00
mdipierro 82cf2eb44d Merge pull request #1851 from ilvalle/fix_1828
fix layout
2018-02-09 12:29:21 -06:00
mdipierro 31514060d7 Merge pull request #1847 from leonelcamara/issuesjanuary
fix print statements in a couple of scripts
2018-02-09 12:28:09 -06:00
mdipierro 1d489d0fde Merge pull request #1846 from ilvalle/fix_scheduler_async_py37
fix py37 conflict due to async definition in scheduler
2018-02-09 12:27:49 -06:00
mdipierro f161cc4f3b Merge pull request #1845 from ilvalle/fix_urllib_urlencode
fix few py3 urllib.urlencode
2018-02-09 12:27:29 -06:00
mdipierro 9e02a768cd Merge pull request #1844 from ilvalle/fix_STYLE_py3
fix py3 STYLE and SCRIPT tags
2018-02-09 12:27:01 -06:00
mdipierro fce88037bd Merge pull request #1843 from timnyborg/master
Allow choosing a SAML entityid
2018-02-09 12:26:34 -06:00
ilvalle 009d5ce48c fix layout, close #1828, thanks @carpaIdea 2018-02-08 20:15:04 +01:00
Leonel Câmara 521d5bce97 fix print statements in a couple of scripts 2018-02-06 19:48:11 +00:00
ilvalle d1efc8b55d fix py37 conflict due to async definition in scheduler 2018-02-04 10:02:14 +01:00
ilvalle ad3c69155b fix few urllib.urlencode, close #1841 2018-02-04 09:58:16 +01:00
ilvalle 421aec162a fix py3 STYLE and SCRIPT tags, close #1835 2018-02-04 09:41:57 +01:00
Tim Nyborg 072311fd2c Revert
Committed in error
2018-02-02 11:23:48 +00:00
Tim Nyborg 5dcbae0b37 Update saml2_auth.py
Pass along any _next url var as part of the outstanding queries, so web2py will know where to send the user once they come back from singing on.  

Useful if the SAML auth is part of a CAS, because otherwise the user is sent from the CAS consumer -> CAS -> SSO -> CAS, and is never returned to the consumer application
2018-02-02 11:03:04 +00:00
Tim Nyborg 76f3384aae Allow choosing a saml entityid
Allows you to pick an entityid when using federation XML data or an MDQ with many entries.  Pysaml2's MDQ metadata is loaded lazily, after you provide a key, so it's necessary to pass the entityid as a key
2018-02-01 14:04:47 +00:00
mdipierro f9606fabde Merge branch 'master' of github.com:web2py/web2py 2018-01-12 17:07:07 -06:00
mdipierro 623f3b9947 Merge pull request #1834 from ilvalle/avoid_garbage_collect
Added the web2py_disable_garbage_collect option to avoid garbage collect
2018-01-12 17:06:05 -06:00
mdipierro f4203ae4d6 Merge pull request #1833 from cccaballero/datewidget
Fixed welcome app date and datetime widget #1831
2018-01-07 18:36:00 -06:00
mdipierro 089359f3ea Merge pull request #1832 from roaldatwork/cas_provider_py_compatibilty
made cas_provider response py3 compatible
2018-01-07 18:35:34 -06:00
mdipierro 8c9cfff578 Merge pull request #1826 from roaldatwork/p3_admin_translator
made sorting of translation keys py3 safe
2018-01-07 18:35:10 -06:00
mdipierro 699513b961 Merge pull request #1824 from ilvalle/fix_urllib
Fix py3 urllib quote_plus
2018-01-07 18:34:42 -06:00
mdipierro b1a9b67c54 Merge pull request #1823 from skz169/skz169-patch-fix-pip
Fix dependencies in setup.py
2018-01-07 18:34:11 -06:00
mdipierro 8fe4d860a1 Merge pull request #1821 from mrdumpty/patch-1
Update db.py
2018-01-07 18:33:56 -06:00
ilvalle b7b16da08f Added the web2py_disable_garbage_collect option to avoid garbage collect 2018-01-07 18:53:33 +01:00
Carlos Cesar Caballero Díaz 6994cee6c9 Fixed wellcome app date and datetime widget #1831 2018-01-05 10:32:46 -05:00
Roald Osinga 9bf8ca9c3b made cas_provider response py3 compatible 2018-01-05 11:49:20 +01:00
Roald Osinga 35a1f6f342 made sorting of translation keys py3 safe 2017-12-13 11:10:08 +01:00
ilvalle f048e45494 removed pycrypto dep 2017-12-12 20:38:56 +01:00
ilvalle cfdda257ed testing py36 on windows 2017-12-12 20:21:54 +01:00
ilvalle dca4639b85 fix urllib quote_plus, close #1822 2017-12-12 19:23:19 +01:00
Sergei K 4ecdc2bc73 Update setup.py 2017-12-11 19:59:28 +05:00
mrdumpty c7d91108bb Update db.py
Possible typo?
2017-12-07 17:10:05 +04:00
mdipierro 4697e0e45b ignore computed fields 2017-12-04 23:45:03 -06:00
mdipierro 4f51647b2f align login menu to right 2017-12-02 14:15:06 -06:00
mdipierro 79b877f6b4 create forms should not display default values for readonly fields (third attempt) 2017-12-02 13:52:20 -06:00
mdipierro 553b7cf51a create forms should not display default values for readonly fields 2017-12-02 13:46:22 -06:00
mdipierro 20411ce45f create forms should not display default values for readonly fields 2017-12-02 13:37:42 -06:00
mdipierro b11d6f6498 removed share 2017-12-02 13:30:19 -06:00
mdipierro 44c3c3b0e4 Merge branch 'master' of github.com:web2py/web2py 2017-12-02 13:03:13 -06:00
mdipierro e9dcac4ecd Merge pull request #1818 from ilvalle/py3.6dev
fix include_meta for py35, added tests
2017-12-02 13:01:12 -06:00
mdipierro 4a2a02d1fe fixed display of computed fields 2017-12-02 12:52:15 -06:00
ilvalle 622b29366a fix include_meta for py35, added tests. close #1816 2017-12-02 11:03:46 +01:00
mdipierro 130e37d708 Merge pull request #1814 from vinyldarkscratch/patch-1
Fix update_languages script (use application folder)
2017-11-22 14:51:19 -06:00
mdipierro 160ec9b044 Merge pull request #1811 from timnyborg/master
Update SQLFORM deleted = False if readonly
2017-11-22 14:50:39 -06:00
mdipierro ee8c401020 Merge pull request #1807 from abastardi/janrain-fix
Fix Janrain due to change in behavior
2017-11-22 14:50:30 -06:00
mdipierro c8a7943e8f Merge pull request #1806 from leonelcamara/patch-12
Update list of TLDs
2017-11-22 14:50:21 -06:00
Vinyl Darkscratch 7bbf6b93f3 Actually skip the default language 2017-11-20 13:02:32 -08:00
Vinyl Darkscratch 3410810c1c Fix update_languages script (use application folder)
Admittedly after preparing this script for others to use, I kind of forgot to test it...  Whoops!  This is an update that should fix the script and make Gluon happy.  Instead of taking the languages folder, it takes the application folder, to help resolve conflicts with Gluon.  The script was also originally configured to run with the language folder being the current working directory, and as such, the changes to the language files would never be saved.
2017-11-20 12:54:07 -08:00
ilvalle 26081782c8 testing development environments 2017-11-18 12:44:46 +01:00
Tim Nyborg b732ab6d53 Update SQLFORM deleted = False if readonly
fixes the following syntax:

form = SQLFORM(table, record, readonly=readonly, deletable=True)

form.process()

if form.deleted:
   pass
elif form.accepted:
   pass
2017-11-17 16:53:27 +00:00
abastardi 071622b9d9 Fix Janrain due to change in behavior
Janrain has started providing the login token via a POST request
in addition to including it in the query string. This results
in request.vars.token being a list containing two copies of the
token, which breaks the web2py code. This change keeps just a single
copy of the token.
2017-11-15 14:52:19 -05:00
Leonel Câmara fd0f169a55 Update list of TLDs 2017-11-14 18:20:13 +00:00
mdipierro c9fd0fd71e fixed test because of layout change 2017-11-14 00:55:59 -06:00
mdipierro d59e7f35ed fixed toolbar/configuration issue 2017-11-14 00:50:44 -06:00
mdipierro 999e7b7512 fixed layout in lack of configuration 2017-11-14 00:47:40 -06:00
mdipierro 7035398681 2.16.1 2017-11-13 23:50:20 -06:00
mdipierro 2c2c9d3aa2 moved to bootstrap4 2017-11-13 23:48:54 -06:00
mdipierro 65b3e6dda9 support for bootstrap4 2017-11-13 23:29:34 -06:00
mdipierro d424e5d317 Merge pull request #1610 from geomapdev/dal-enhancements
handle reference fields for keyed tables
2017-11-13 21:24:07 -06:00
mdipierro a1417df67c Merge branch 'master' of github.com:web2py/web2py 2017-11-13 21:15:04 -06:00
mdipierro 551c19bcaf Merge pull request #1761 from josedesoto/issue/update_role_on_update_profile
Update groups on edit profile
2017-11-13 21:14:02 -06:00
mdipierro 8aa07760d2 Merge pull request #1803 from leonelcamara/patch-10
Fix #1715
2017-11-13 21:13:06 -06:00
mdipierro 8bd8db5edc Merge pull request #1804 from leonelcamara/patch-11
Update supported python versions
2017-11-13 21:12:36 -06:00
mdipierro 7c1fb6643e fllowing pydal v17.11 2017-11-13 21:12:06 -06:00
Leonel Câmara e44a12eaf8 Update supported python versions 2017-11-13 22:40:37 +00:00
Leonel Câmara 12253ab757 Fix #1715 2017-11-13 15:31:05 +00:00
mdipierro 30b3d84f24 fixed options when type is list 2017-11-10 22:31:35 -06:00
mdipierro 166e268308 improved handling of regex and options field attributes 2017-11-10 18:50:52 -06:00
mdipierro 14e58276cf form handling of f.options, f.regex, f.listable, f.rearchable 2017-11-10 18:41:41 -06:00
mdipierro eb7222aa92 Merge pull request #1802 from leonelcamara/fixsqltable
Fix a problem with Expressions in SQLTABLE
2017-11-10 18:12:50 -06:00
mdipierro 009a0b87f2 Merge pull request #1801 from leonelcamara/i1800
Fixes #1800
2017-11-10 18:12:16 -06:00
mdipierro aeb0244b2c Merge pull request #1799 from vinyldarkscratch/master
Add two scripts
2017-11-10 18:12:03 -06:00
mdipierro 57522ddeb1 Merge pull request #1797 from BigManWalter/master
Set entry animation to 'fadeIn' and provide an easy switch for 'slideDown'
2017-11-10 18:10:13 -06:00
mdipierro 934042fc04 Merge pull request #1796 from vitkarpenko/middleware_status_code
gluon.http expects status code as integer while LazyWSGI generates str
2017-11-10 18:09:37 -06:00
Leonel Câmara 4bfa9c1686 Fix a problem with Expressions in SQLTABLE possibly fixes #1735 and fixes web2py/pydal#487 2017-11-08 22:21:51 +00:00
Leonel Câmara 925f928843 Copy all CRYPT attributes thanks @abastardi 2017-11-08 11:53:29 +00:00
mdipierro 2f5eb409b6 show computed fields in readonly mode in appadmin 2017-11-08 00:40:47 -06:00
Leonel Câmara 228d3c41b6 Fixes #1800 2017-11-07 23:34:35 +00:00
Vinyl Darkscratch 3ab91b9fe9 Add check_lang_progress script 2017-11-06 01:42:25 -08:00
Vinyl Darkscratch 5db3b21437 Add update_languages script 2017-11-06 01:42:14 -08:00
Eric Waldman 2aa8fd22a2 Set entry animation to 'fadeIn' and provide an easy switch for 'slideDown' 2017-11-03 15:17:18 -04:00
Vitaly Karpenko d5eb8d8f17 gluon.http expects status code as integer 2017-11-02 13:52:25 +03:00
mdipierro c4e08eeeb3 Merge pull request #1793 from sherdim/master
A step to PY3  closes #1792 and may be closes #1727
2017-11-01 18:59:52 -05:00
geomapdev c1a3d5d67e sync with master branch 2017-10-31 14:47:07 -07:00
geomapdev b2841de6f3 Merge remote-tracking branch 'upstream/master' 2017-10-31 14:42:19 -07:00
Дим Щ c7d415fefd compatible unicode 2017-10-28 19:32:48 +03:00
Дим Щ 2e572aee9a Update custom_import.py 2017-10-28 19:29:55 +03:00
Дим Щ dd14d1c8c9 Add reload 2017-10-28 19:28:51 +03:00
Дим Щ afa5fac074 Add reload 2017-10-28 19:26:34 +03:00
mdipierro a5b76f1dec Merge branch 'master' of github.com:web2py/web2py 2017-10-18 22:14:27 -05:00
mdipierro 1e586b32a4 Merge pull request #1789 from timnyborg/master
Fix setting 'ac' in request.vars
2017-10-18 22:13:16 -05:00
mdipierro dfcffe1a9d Merge pull request #1785 from tiagobar/master
Enhances/fixes validators/is_empty() for Python 3 compatibility, fix #1764
2017-10-18 22:12:09 -05:00
Tim Nyborg 2e6f529dfd Fix setting 'ac' in request.vars 2017-10-12 17:18:06 +01:00
Tim Nyborg 0ae3ee506f Merge pull request #1 from web2py/master
rebase
2017-10-12 17:16:56 +01:00
geomapdev 6b103f7e3a updated test_validators IS_IN_DB 2017-10-10 08:42:17 -07:00
geomapdev 02c32ebace update gluon unittest for dal reference fields 2017-10-09 11:22:48 -07:00
geomapdev 603cc7092a dal.py more code cleanup 2017-10-04 10:39:42 -07:00
geomapdev 8590aae2e8 dal.py code cleanup 2017-10-04 10:34:48 -07:00
tiago.bar 511245a68c Unit test for #1764 fix 2017-09-23 20:21:14 -03:00
tiago.bar 4a347a4f78 Enhances/fixes validators/is_empty() for Python 3 compatibility, fix #1764 2017-09-22 13:04:47 -03:00
Jose de Soto 5f4c47729b Removed a tab and replaced by spaces 2017-09-21 10:17:17 +02:00
mdipierro 5975e4767f Merge pull request #1766 from abastardi/tsv_export
Fix grid TSV export bug
2017-09-21 00:23:17 -05:00
mdipierro 1800304ade Merge pull request #1763 from abastardi/compiled_views
Fix bug with compiled views in compiled-only apps
2017-09-21 00:22:53 -05:00
mdipierro 9af0d8c3c9 Merge pull request #1758 from leonelcamara/patch-9
Fix #1757
2017-09-21 00:21:10 -05:00
mdipierro 2e2d3482e4 Merge pull request #1756 from leonelcamara/i1752
Make _compat accept ducks
2017-09-21 00:20:54 -05:00
mdipierro 48cc63741e Merge pull request #1755 from leonelcamara/i1753
Fixes #1753
2017-09-21 00:20:26 -05:00
abastardi a605e43fa6 Fix grid TSV export bug 2017-09-14 18:49:31 -04:00
abastardi 8eda21ca86 Fix bug with compiled views in compiled-only apps
Compiled views were not being executed in apps containing only compiled
files (i.e., generated via the admin "Pack compiled" functionality),
resulting in "Invalid view" errors for all pages.
2017-09-13 11:48:25 -04:00
Jose de Soto e8cf50326d When profile is updated self._update_session_user(user) set session.user_groups to None. self.update_groups() needs to be done. 2017-09-13 11:21:03 +02:00
mdipierro 27c9250efb fixed book link 2017-09-07 12:46:36 -05:00
Leonel Câmara 3ecdd1c11b Fix #1757 2017-09-07 10:18:41 +01:00
Leonel Câmara 912c22d593 Don't use gluon.utf8.Utf8 with py3 there's no need for it and it breaks stuff 2017-09-06 16:22:50 +01:00
Leonel Câmara 4b38186b51 simplify condition if you ain't got decode you ain't bytes 2017-09-06 15:37:51 +01:00
Leonel Câmara 83f9016528 fix in py3 too 2017-09-06 15:33:47 +01:00
Leonel Câmara a6044068cd Fixes #1751 2017-09-06 15:31:30 +01:00
Leonel Câmara 4aefb93ab4 Fixes #1752 2017-09-06 15:28:39 +01:00
Leonel Câmara 2861dc4215 Fixes #1753 2017-09-05 15:46:31 +01:00
mdipierro 087280ec17 fixed Makefile _ssd 2017-09-01 22:47:54 -05:00
mdipierro d06a1f9dc6 R-2.15.4 2017-09-01 22:40:20 -05:00
mdipierro c06fc67064 following pydal 17.08 2017-08-31 17:31:43 -05:00
mdipierro bd167aa94a Merge pull request #1749 from liuyigh/patch-1
Minor error in EOF instructions
2017-08-31 17:29:56 -05:00
Yi Liu c9a71a7055 MInor error in EOF instructions
start -> stop
2017-08-29 11:26:22 -07:00
mdipierro 02e50cadbc Merge pull request #1746 from abastardi/disable-submit
Fix submit button disable bug
2017-08-28 11:25:55 -05:00
mdipierro 9f69ab9753 Merge pull request #1743 from ilvalle/fix_ticket_compile_views
Fix view file name in ticket traceback
2017-08-28 11:25:40 -05:00
mdipierro 56d10a40c6 Merge pull request #1741 from leonelcamara/patch-8
execfile shim for python 3 compatibility
2017-08-28 11:25:28 -05:00
mdipierro 76b09393e4 Merge pull request #1738 from leonelcamara/patch-7
Fixes #1737
2017-08-28 11:25:07 -05:00
abastardi e880da0d0e Fix submit button disable bug
When a form includes more than one submit button, after being disabled
all buttons are re-enabled with the value of the first button rather
than having their original values restored. This change iterates over
each button separately to disable and enable.
2017-08-23 11:43:40 -04:00
ilvalle 9694c66703 Fix view file name in ticket traceback, fix #1740, fix #1676 2017-08-19 10:07:52 +02:00
Leonel Câmara f22e3a7624 execfile shim for python 3 compatibility
Fixes #1739
2017-08-16 16:35:46 +01:00
Leonel Câmara bd7ee209ea Fixes #1737
getproxies is in urllib.request for python 3
2017-08-15 15:34:35 +01:00
mdipierro 5e6c3dba81 Merge pull request #1732 from lkrn/master
fixes #1724
2017-08-13 13:55:09 -05:00
LAdm 135f41041d fixes #1724
call func instead of module in url_is_acceptable
2017-08-10 06:49:36 +02:00
mdipierro 1d0f322d09 Merge pull request #1731 from leonelcamara/fix-include-files
Fix include files
2017-08-09 15:46:51 -05:00
Leonel Câmara 892fba9e54 fix unordered include_files result
Now only concats adjacent internal to the application stuff, this also Fixes #1673
2017-08-09 01:08:28 +01:00
Leonel Câmara 6e5c8b62cc Make test_include_files demand the same order 2017-08-09 01:05:37 +01:00
mdipierro 852a9e0127 Merge pull request #1726 from BuhtigithuB/feature/has-membership-cached-true
auth.has_membership(cached=True) check membership in auth.user_groups
2017-08-08 12:19:26 -05:00
Richard Vézina 485f868cd1 auth.has_membership(cached=True) check membership in auth.user_groups 2017-08-08 11:40:07 -04:00
mdipierro de8b2a477b Merge pull request #1722 from leonelcamara/patch-5
mobilize is back
2017-08-07 23:29:06 -05:00
Leonel Câmara 8533fa0d00 put is_mobile and is_tablet in the result of user_agent() 2017-08-08 00:50:55 +01:00
mdipierro 7e96ecafd7 R-2.15.3 2017-08-07 07:41:11 -05:00
mdipierro 86ea728f4f R-2.15.3 2017-08-07 07:32:58 -05:00
mdipierro a29947f298 websocket_messaging in Python 3 #1696, thanks hirolau 2017-08-07 07:27:28 -05:00
mdipierro dc29f33365 addressed __ssl.pyd issue #1716, thanks Jhleite 2017-08-07 07:18:42 -05:00
mdipierro 834460f0cc Merge pull request #1723 from leonelcamara/patch-6
Small unicode compatibility py3 fixes
2017-08-07 07:09:30 -05:00
mdipierro 5353e17c66 Merge pull request #1720 from leonelcamara/patch-4
Fix BEAUTIFY trying to display uploaded file contents
2017-08-07 07:08:58 -05:00
mdipierro d2022ca500 Merge pull request #1719 from leonelcamara/patch-3
Fix response.download with nonasccii filenames
2017-08-07 07:08:33 -05:00
mdipierro c560f607af Merge pull request #1710 from willimoa/issue/1704
Fix Issue 1709
2017-08-07 07:08:10 -05:00
Leonel Câmara 3eea1f68f4 Make sure you return bytes when you str(body) 2017-08-07 00:20:29 +01:00
Leonel Câmara 10b6b93cb2 minor py3 compatibility change 2017-08-07 00:17:43 +01:00
Leonel Câmara 0b41ed36f9 mobilize is back
Fixes #1721
2017-08-06 19:20:01 +01:00
Leonel Câmara 90e20a4f39 Fix BEAUTIFY trying to display uploaded file contents
Fixes #1717
2017-08-06 17:01:27 +01:00
Leonel Câmara a744835f21 Fix response.download with nonasccii filenames
Fixes #1718
2017-08-05 13:27:44 +01:00
mdipierro ebc614bf91 fixed has_ssl, thanks leonel 2017-08-04 10:21:43 -05:00
mdipierro b7270df9c3 fixed issue topic/web2py/UnN6AyOh2Lg thanks Paul 2017-08-04 09:58:05 -05:00
Andrew Willimott 4a47bb8e83 d3_graph_model fixed, no longer hardcoded for a “db” database 2017-08-02 21:47:26 +12:00
mdipierro 213c4ee7d1 fixed use of whitespaces 2017-08-01 10:26:33 -05:00
mdipierro 7088b74d42 Merge pull request #1705 from josedesoto/enhancement/1557
Enhancement/1557
2017-08-01 09:46:55 -05:00
mdipierro 752b5a8cdb Merge pull request #1704 from josedesoto/enhancement/response_json_sort_paramenter
Allow response.json to be sortable or not
2017-08-01 09:46:11 -05:00
mdipierro 56e6d682d6 Merge pull request #1702 from leonelcamara/i1699
Fix autocomplete called after user_signature chk
2017-08-01 09:46:00 -05:00
mdipierro c1881fa205 Merge pull request #1701 from jkotyz/issue/1700
Fixes 1700 - utc and local time inconsistency in AuthJWT
2017-08-01 09:45:46 -05:00
mdipierro cc2cae5de4 Merge pull request #1692 from timnyborg/master
Fix #1691: Passing tables to SQLFORM.factory() fails
2017-08-01 09:45:21 -05:00
mdipierro cedd74c1ad Merge pull request #1690 from josedesoto/issue/1689
del_membership prevent update self user if user_id has a value
2017-08-01 09:44:41 -05:00
Jose de Soto d5167f2ed6 change_password_url parameter for alternate login methods 2017-07-31 19:00:24 +02:00
Jose de Soto 1014d3e86e new parameter to auto create or not users with alternate login methods 2017-07-31 18:33:15 +02:00
Jose de Soto f3bba29287 Allow response.json to be sortable or not 2017-07-31 17:46:46 +02:00
mdipierro cd2ec98a26 grid.dbset 2017-07-29 01:31:25 -05:00
mdipierro 0e613f2d7f allow DAL(..., adapter_args=dict(migrator=InDBMigrator)) 2017-07-28 16:05:35 -05:00
Leonel Câmara 561828fa60 Fix autocomplete called after user_signature chk
Fixes #1699 which was caused by the autocomplete widget only being called after a user_signature check and the signature not being there.  

The default values for user_signature and hash_vars make this work with the grid when it has the user_signature=True option without any problem when that isn't the case. Users can disable it if they want or change it according to what they're verifying in their controllers.   
  
The autocomplete widget will still fail with the default kwargs in a situation where it is called in a controller function that first verifies the URL signature with hash_vars=True.  
  
For users having that problem, there's no way around this without inserting a security flaw, so they must sadly change their application code to give the autocomplete widget the needed user_signature and hash_vars argument.
2017-07-28 02:20:54 +01:00
Jan Kotyz 19efbfecfa Fixes 1700 2017-07-27 11:27:41 +02:00
Tim Nyborg d43604c3ff List comprehension rather than loop
I've now learned this is quicker
2017-07-24 10:15:12 +01:00
Tim Nyborg ec21f72ce3 Fixes #1691
Clone fields, but leave tables untouched in factory()
2017-07-21 10:20:08 +01:00
Jose de Soto aa0313c59b del_membership prevent update self user if user_id has a value 2017-07-21 10:41:58 +02:00
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
geomapdev 69e6e79e23 Update dal.py
updated to handle references without format
2017-05-01 08:39:31 -07: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
geomapdev 3292f760ca handle reference fields for keyed tables 2017-04-20 10:21:16 -07: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
134 changed files with 7956 additions and 4869 deletions
+1
View File
@@ -13,6 +13,7 @@
*.orig
Thumbs.db
.DS_Store
*.DS_Store
index.yaml
routes.py
logging.conf
+17 -2
View File
@@ -1,13 +1,25 @@
language: python
sudo: false
sudo: required
cache: pip
dist: "trusty"
python:
- '2.7'
- 'pypy'
- '3.5'
- '3.6'
- '3.6-dev'
- '3.7-dev'
- 'pypy-5.3.1'
- 'pypy3.5-5.7.1-beta'
matrix:
allow_failures:
- python: 'pypy3.5-5.7.1-beta'
- python: '3.6-dev'
- python: '3.7-dev'
install:
- pip install -e .
@@ -32,3 +44,6 @@ notifications:
addons:
postgresql: "9.4"
apt:
packages:
- postgresql-9.4-postgis-2.3
+44 -6
View File
@@ -1,8 +1,36 @@
## 2.16.1
- pydal 17.11
- bootstrap 4
- better welcome examples
- many bug fixes
## 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.1-4
- pydal 17.08
- 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 +38,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
+10 -6
View File
@@ -30,11 +30,7 @@ update:
wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
rmfiles:
### clean up baisc apps
rm -f routes.py
rm -rf applications/*/sessions/*
@@ -46,6 +42,12 @@ src:
rm -rf applications/admin/uploads/*
rm -rf applications/welcome/uploads/*
rm -rf applications/examples/uploads/*
src:
### Use semantic versioning
echo 'Version 2.16.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
#make clean
# make rmfiles
### make welcome layout and appadmin the default
cp applications/welcome/views/appadmin.html applications/admin/views
cp applications/welcome/views/appadmin.html applications/examples/views
@@ -54,7 +56,7 @@ src:
### build web2py_src.zip
echo '' > NEWINSTALL
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
cd ..; zip -r --exclude=**.git** web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
mdp:
make src
@@ -97,6 +99,8 @@ win:
cp -r applications/welcome ../web2py_win/web2py/applications
cp -r applications/examples ../web2py_win/web2py/applications
cp applications/__init__.py ../web2py_win/web2py/applications
# per https://github.com/web2py/web2py/issues/1716
mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done'
cd ../web2py_win; zip -r web2py_win.zip web2py
mv ../web2py_win/web2py_win.zip .
run:
+1 -1
View File
@@ -1 +1 @@
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48
Version 2.16.1-stable+timestamp.2017.11.13.23.50.07
+49 -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,51 @@ 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"
"""
nodes = []
links = []
for database in databases:
db = eval_in_global_env(database)
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
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(
+6 -4
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))
@@ -959,8 +963,7 @@ def edit_language():
form = SPAN(strings['__corrupted__'], _class='error')
return dict(filename=filename, form=form)
keys = sorted(strings.keys(), lambda x, y: cmp(
unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower()))
keys = sorted(strings.keys(), key=lambda x: to_native(x).lower())
rows = []
rows.append(H2(T('Original/Translation')))
@@ -1861,7 +1864,6 @@ def user():
def reload_routes():
""" Reload routes.py """
import gluon.rewrite
gluon.rewrite.load()
redirect(URL('site'))
@@ -1960,7 +1962,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/" +
@@ -5,7 +5,7 @@ import re
import gzip
import tarfile
from gluon.contrib.simplejsonrpc import ServerProxy
from gluon._compat import StringIO, ProtocolError
from gluon._compat import StringIO, ProtocolError, urlencode, urllib2
def deploy():
response.title = T('Deploy to pythonanywhere')
@@ -26,9 +26,8 @@ def create_account():
except ProtocolError as error:
pass
import urllib, urllib2
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
data = urllib.urlencode(request.vars)
data = urlencode(request.vars)
req = urllib2.Request(url, data)
try:
+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
+86 -28
View File
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!');
}
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
var animateIn = 'fadeIn';
// var animateIn = 'slideDown';
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -38,8 +42,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 +67,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) {
/*
@@ -148,7 +182,7 @@
},
/* manage errors in forms */
manage_errors: function (target) {
$('div.error', target).hide().slideDown('slow');
$('div.error', target).hide()[animateIn]('slow');
},
after_ajax: function (xhr) {
/* called whenever an ajax request completes */
@@ -168,7 +202,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();
@@ -232,13 +267,17 @@
}
});
/* help preventing double form submission for normal form (not LOADed) */
$(doc).on('submit', 'form', function () {
var submit_button = $(this).find(web2py.formInputClickSelector);
web2py.disableElement(submit_button);
$(doc).on('submit', 'form', function (e) {
var submit_buttons = $(this).find(web2py.formInputClickSelector);
submit_buttons.each(function() {
web2py.disableElement($(this));
})
/* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () {
web2py.enableElement(submit_button);
submit_buttons.each(function() {
web2py.enableElement($(this));
});
}, 5000);
});
doc.ajaxSuccess(function (e, xhr) {
@@ -289,7 +328,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 +355,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) {
@@ -562,8 +617,8 @@
flash: function (message, status) {
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
flash.text(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
},
hide_flash: function () {
$('.w2p_flash').fadeOut(0).html('');
@@ -577,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], target);
var tr = $('#' + triggers[id][k] + '__row', target);
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
else tr.hide();
}
};
@@ -667,8 +722,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 +736,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);
});
},
@@ -767,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/
+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'}}
+49 -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,51 @@ 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"
"""
nodes = []
links = []
for database in databases:
db = eval_in_global_env(database)
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
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)
@@ -3,9 +3,9 @@
- **Created by a community of professionals** and University professors in Computer Science and Software Engineering.
- **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future.
- **Easy to run.** It requires no installation and no configuration.
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.5/2.6/2.7/pypy, or Java with Jython.
- **Runs with** Apache, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Oracle, IBM DB2, Informix, Ingres, and Google App Engine.
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.7/3.5/3.6/pypy.
- **Runs with** Apache, Nginx, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Sybase, Oracle, IBM DB2, Informix, Ingres, MongoDB, and Google App Engine.
- **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution.
- **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable.
- **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF).
File diff suppressed because one or more lines are too long
+86 -28
View File
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!');
}
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
var animateIn = 'fadeIn';
// var animateIn = 'slideDown';
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -38,8 +42,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 +67,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) {
/*
@@ -148,7 +182,7 @@
},
/* manage errors in forms */
manage_errors: function (target) {
$('div.error', target).hide().slideDown('slow');
$('div.error', target).hide()[animateIn]('slow');
},
after_ajax: function (xhr) {
/* called whenever an ajax request completes */
@@ -168,7 +202,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();
@@ -232,13 +267,17 @@
}
});
/* help preventing double form submission for normal form (not LOADed) */
$(doc).on('submit', 'form', function () {
var submit_button = $(this).find(web2py.formInputClickSelector);
web2py.disableElement(submit_button);
$(doc).on('submit', 'form', function (e) {
var submit_buttons = $(this).find(web2py.formInputClickSelector);
submit_buttons.each(function() {
web2py.disableElement($(this));
})
/* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () {
web2py.enableElement(submit_button);
submit_buttons.each(function() {
web2py.enableElement($(this));
});
}, 5000);
});
doc.ajaxSuccess(function (e, xhr) {
@@ -289,7 +328,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 +355,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) {
@@ -562,8 +617,8 @@
flash: function (message, status) {
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
flash.text(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
},
hide_flash: function () {
$('.w2p_flash').fadeOut(0).html('');
@@ -577,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], target);
var tr = $('#' + triggers[id][k] + '__row', target);
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
else tr.hide();
}
};
@@ -667,8 +722,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 +736,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);
});
},
@@ -767,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/
+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>
@@ -48,7 +48,7 @@
</tr>
<tr>
<td>
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
<a class="btn btn180 rounded green" href="http://mdipierro.github.io/web2py/web2py_manual_5th.pdf">Manual</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</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}}
+59 -67
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
@@ -397,12 +392,11 @@ def ccache():
cache.disk.clear()
session.flash += T("Disk Cleared")
redirect(URL(r=request))
try:
from guppy import hpy
hp = hpy()
from pympler.asizeof import asizeof
except ImportError:
hp = False
asizeof = False
import shelve
import os
@@ -456,15 +450,16 @@ def ccache():
ram['ratio'] = 0
for key, value in iteritems(cache.ram.storage):
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
if asizeof:
ram['bytes'] += asizeof(value[1])
ram['objects'] += 1
ram['entries'] += 1
if value[0] < ram['oldest']:
ram['oldest'] = value[0]
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']
@@ -473,9 +468,9 @@ def ccache():
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
if hp:
disk['bytes'] += hp.iso(value[1]).size
disk['objects'] += hp.iso(value[1]).count
if asizeof:
disk['bytes'] += asizeof(value[1])
disk['objects'] += 1
disk['entries'] += 1
if value[0] < disk['oldest']:
disk['oldest'] = value[0]
@@ -515,7 +510,7 @@ def ccache():
total['keys'] = key_table(total['keys'])
return dict(form=form, total=total,
ram=ram, disk=disk, object_stats=hp != False)
ram=ram, disk=disk, object_stats=asizeof != False)
def table_template(table):
@@ -564,57 +559,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 +643,51 @@ 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"
"""
nodes = []
links = []
for database in databases:
db = eval_in_global_env(database)
for tablename in db.tables:
fields = []
for field in db[tablename]:
f_type = field.type
if not isinstance(f_type,str):
disp = ' '
elif f_type == 'string':
disp = field.length
elif f_type == 'id':
disp = "PK"
elif f_type.startswith('reference') or \
f_type.startswith('list:reference'):
disp = "FK"
else:
disp = ' '
fields.append(dict(name= field.name, type=field.type, disp = disp))
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)
+23 -26
View File
@@ -1,26 +1,35 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
# -------------------------------------------------------------------------
# This is a sample controller
# - index is the default action of any application
# - user is required for authentication and authorization
# - download is for downloading files uploaded in the db (does streaming)
# this file is released under public domain and you can use without limitations
# -------------------------------------------------------------------------
# ---- example index page ----
def index():
"""
example action using the internationalization operator T and flash
rendered by views/default/index.html or views/generic.html
if you need a simple wiki simply replace the two lines below with:
return auth.wiki()
"""
response.flash = T("Hello World")
return dict(message=T('Welcome to web2py!'))
# ---- API (example) -----
@auth.requires_login()
def api_get_user_email():
if not request.env.request_method == 'GET': raise HTTP(403)
return response.json({'status':'success', 'email':auth.user.email})
# ---- Smart Grid (example) -----
@auth.requires_membership('admin') # can only be accessed by members of admin groupd
def grid():
response.view = 'generic.html' # use a generic view
tablename = request.args(0)
if not tablename in db.tables: raise HTTP(403)
grid = SQLFORM.smartgrid(db[tablename], args=[tablename], deletable=False, editable=False)
return dict(grid=grid)
# ---- Embedded wiki (example) ----
def wiki():
auth.wikimenu() # add the wiki to the menu
return auth.wiki()
# ---- Action for login/register/etc (required for auth) -----
def user():
"""
exposes:
@@ -39,7 +48,7 @@ def user():
"""
return dict(form=auth())
# ---- action to server uploaded static content (required) ---
@cache.action()
def download():
"""
@@ -47,15 +56,3 @@ def download():
http://..../[app]/default/download/[filename]
"""
return response.download(request, db)
def call():
"""
exposes services. for example:
http://..../[app]/default/call/jsonrpc
decorate with @services.jsonrpc the functions to expose
supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
"""
return service()
+2 -2
View File
@@ -8,8 +8,8 @@
'%s %%{row} deleted': '%s %%{fila} %%{eliminada}',
'%s %%{row} updated': '%s %%{fila} %%{actualizada}',
'%s selected': '%s %%{seleccionado}',
'%Y-%m-%d': '%d/%m/%A',
'%Y-%m-%d %H:%M:%S': '%d/%m/%A %H:%M:%S',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'(**%.0d MB**)': '(**%.0d MB**)',
'(something like "it-it")': '(algo como "it-it")',
'**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}',
+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',
+48 -25
View File
@@ -1,12 +1,19 @@
# -*- coding: utf-8 -*-
# -------------------------------------------------------------------------
# AppConfig configuration made easy. Look inside private/appconfig.ini
# Auth is for authenticaiton and access control
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig
from gluon.tools import Auth
# -------------------------------------------------------------------------
# This scaffolding model makes your app work on Google App Engine too
# File is released under public domain and you can use without limitations
# -------------------------------------------------------------------------
if request.global_settings.web2py_version < "2.14.1":
raise HTTP(500, "Requires web2py 2.13.3 or newer")
if request.global_settings.web2py_version < "2.15.5":
raise HTTP(500, "Requires web2py 2.15.5 or newer")
# -------------------------------------------------------------------------
# if SSL/HTTPS is properly configured and you want all HTTP requests to
@@ -14,23 +21,18 @@ if request.global_settings.web2py_version < "2.14.1":
# -------------------------------------------------------------------------
# request.requires_https()
# -------------------------------------------------------------------------
# app configuration made easy. Look inside private/appconfig.ini
# -------------------------------------------------------------------------
from gluon.contrib.appconfig import AppConfig
# -------------------------------------------------------------------------
# once in production, remove reload=True to gain full speed
# -------------------------------------------------------------------------
myconf = AppConfig(reload=True)
configuration = AppConfig(reload=True)
if not request.env.web2py_runtime_gae:
# ---------------------------------------------------------------------
# if NOT running on Google App Engine use SQLite or other DB
# ---------------------------------------------------------------------
db = DAL(myconf.get('db.uri'),
pool_size=myconf.get('db.pool_size'),
migrate_enabled=myconf.get('db.migrate'),
db = DAL(configuration.get('db.uri'),
pool_size=configuration.get('db.pool_size'),
migrate_enabled=configuration.get('db.migrate'),
check_reserved=['all'])
else:
# ---------------------------------------------------------------------
@@ -52,12 +54,15 @@ else:
# by default give a view/generic.extension to all actions from localhost
# none otherwise. a pattern can be 'controller/function.extension'
# -------------------------------------------------------------------------
response.generic_patterns = ['*'] if request.is_local else []
response.generic_patterns = []
if request.is_local and not configuration.get('app.production'):
response.generic_patterns.append('*')
# -------------------------------------------------------------------------
# choose a style for forms
# -------------------------------------------------------------------------
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.get('forms.separator') or ''
response.formstyle = 'bootstrap4_inline'
response.form_label_separator = ''
# -------------------------------------------------------------------------
# (optional) optimize handling of static files
@@ -80,27 +85,24 @@ response.form_label_separator = myconf.get('forms.separator') or ''
# (more options discussed in gluon/tools.py)
# -------------------------------------------------------------------------
from gluon.tools import Auth, Service, PluginManager
# host names must be a list of allowed host names (glob syntax allowed)
auth = Auth(db, host_names=myconf.get('host.names'))
service = Service()
plugins = PluginManager()
auth = Auth(db, host_names=configuration.get('host.names'))
# -------------------------------------------------------------------------
# create all tables needed by auth if not custom tables
# create all tables needed by auth, maybe add a list of extra fields
# -------------------------------------------------------------------------
auth.settings.extra_fields['auth_user'] = []
auth.define_tables(username=False, signature=False)
# -------------------------------------------------------------------------
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender')
mail.settings.login = myconf.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False
mail.settings.server = 'logging' if request.is_local else configuration.get('smtp.server')
mail.settings.sender = configuration.get('smtp.sender')
mail.settings.login = configuration.get('smtp.login')
mail.settings.tls = configuration.get('smtp.tls') or False
mail.settings.ssl = configuration.get('smtp.ssl') or False
# -------------------------------------------------------------------------
# configure auth policy
@@ -109,6 +111,27 @@ auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True
# -------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# -------------------------------------------------------------------------
response.meta.author = configuration.get('app.author')
response.meta.description = configuration.get('app.description')
response.meta.keywords = configuration.get('app.keywords')
response.meta.generator = configuration.get('app.generator')
response.show_toolbar = configuration.get('app.toolbar')
# -------------------------------------------------------------------------
# your http://google.com/analytics id
# -------------------------------------------------------------------------
response.google_analytics_id = configuration.get('google.analytics_id')
# -------------------------------------------------------------------------
# maybe use the scheduler
# -------------------------------------------------------------------------
if configuration.get('scheduler.enabled'):
from gluon.scheduler import Scheduler
scheduler = Scheduler(db, heartbeat=configuration.get('heartbeat'))
# -------------------------------------------------------------------------
# Define your tables below (or better in another model file) for example
#
+14 -55
View File
@@ -1,29 +1,6 @@
# -*- coding: utf-8 -*-
# this file is released under public domain and you can use without limitations
# ----------------------------------------------------------------------------------------------------------------------
# Customize your APP title, subtitle and menus here
# ----------------------------------------------------------------------------------------------------------------------
response.logo = A(B('web', SPAN(2), 'py'), XML('&trade;&nbsp;'),
_class="navbar-brand", _href="http://www.web2py.com/",
_id="web2py-logo")
response.title = request.application.replace('_', ' ').title()
response.subtitle = ''
# ----------------------------------------------------------------------------------------------------------------------
# read more at http://dev.w3.org/html5/markup/meta.name.html
# ----------------------------------------------------------------------------------------------------------------------
response.meta.author = myconf.get('app.author')
response.meta.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
# ----------------------------------------------------------------------------------------------------------------------
# your http://google.com/analytics id
# ----------------------------------------------------------------------------------------------------------------------
response.google_analytics_id = None
# ----------------------------------------------------------------------------------------------------------------------
# this is the main application menu add/remove items as required
# ----------------------------------------------------------------------------------------------------------------------
@@ -32,53 +9,42 @@ response.menu = [
(T('Home'), False, URL('default', 'index'), [])
]
DEVELOPMENT_MENU = True
# ----------------------------------------------------------------------------------------------------------------------
# provide shortcuts for development. remove in production
# provide shortcuts for development. you can remove everything below in production
# ----------------------------------------------------------------------------------------------------------------------
def _():
# ------------------------------------------------------------------------------------------------------------------
# shortcuts
# ------------------------------------------------------------------------------------------------------------------
app = request.application
ctr = request.controller
# ------------------------------------------------------------------------------------------------------------------
# useful links to internal and external resources
# ------------------------------------------------------------------------------------------------------------------
if not configuration.get('app.production'):
_app = request.application
response.menu += [
(T('My Sites'), False, URL('admin', 'default', 'site')),
(T('This App'), False, '#', [
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
LI(_class="divider"),
(T('Design'), False, URL('admin', 'default', 'design/%s' % _app)),
(T('Controller'), False,
URL(
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
'admin', 'default', 'edit/%s/controllers/%s.py' % (_app, request.controller))),
(T('View'), False,
URL(
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
'admin', 'default', 'edit/%s/views/%s' % (_app, response.view))),
(T('DB Model'), False,
URL(
'admin', 'default', 'edit/%s/models/db.py' % app)),
'admin', 'default', 'edit/%s/models/db.py' % _app)),
(T('Menu Model'), False,
URL(
'admin', 'default', 'edit/%s/models/menu.py' % app)),
'admin', 'default', 'edit/%s/models/menu.py' % _app)),
(T('Config.ini'), False,
URL(
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
'admin', 'default', 'edit/%s/private/appconfig.ini' % _app)),
(T('Layout'), False,
URL(
'admin', 'default', 'edit/%s/views/layout.html' % app)),
'admin', 'default', 'edit/%s/views/layout.html' % _app)),
(T('Stylesheet'), False,
URL(
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
(T('Database'), False, URL(app, 'appadmin', 'index')),
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % _app)),
(T('Database'), False, URL(_app, 'appadmin', 'index')),
(T('Errors'), False, URL(
'admin', 'default', 'errors/' + app)),
'admin', 'default', 'errors/' + _app)),
(T('About'), False, URL(
'admin', 'default', 'about/' + app)),
'admin', 'default', 'about/' + _app)),
]),
('web2py.com', False, '#', [
(T('Download'), False,
@@ -98,7 +64,6 @@ def _():
]),
(T('Documentation'), False, '#', [
(T('Online book'), False, 'http://www.web2py.com/book'),
LI(_class="divider"),
(T('Preface'), False,
'http://www.web2py.com/book/default/chapter/00'),
(T('Introduction'), False,
@@ -143,9 +108,3 @@ def _():
]),
]
if DEVELOPMENT_MENU:
_()
if "auth" in locals():
auth.wikimenu()
+8 -5
View File
@@ -5,6 +5,8 @@ author = Your Name <you@example.com>
description = a cool new app
keywords = web2py, python, framework
generator = Web2py Web Framework
production = false
toolbar = false
; Host configuration
[host]
@@ -14,7 +16,6 @@ names = localhost:*, 127.0.0.1:*, *:*, *
[db]
uri = sqlite://storage.sqlite
migrate = true
; ignored for sqlite
pool_size = 10
; smtp address and credentials
@@ -25,7 +26,9 @@ login = username:password
tls = true
ssl = true
; form styling
[forms]
formstyle = bootstrap3_inline
separator =
[scheduler]
enabled = false
heartbeat = 1
[google]
analytics_id =
+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
@@ -1,3 +1,11 @@
ul.navbar-nav.navbar-right {
position: absolute;
right: 100px;
}
label, th {
font-weigth: bold;
white-space: nowrap;
}
div.w2p_flash {
background-image: none;
border-radius: 4px;
@@ -108,7 +116,6 @@ select.autocomplete {
background: url(../images/background.jpg) no-repeat center center;
}
body {
padding-top: 60px;
margin-bottom: 60px;
}
header {
@@ -126,8 +133,7 @@ html {
bottom: 0;
width: 100%;
height: 60px;
background: #333;
color: #aaa;
background: #f7f7f7;
}
header h1 {
color: #FFF!important;
@@ -315,3 +321,20 @@ td.w2p_fc,
input[type=checkbox], input[type=radio] {
margin: 4px 4px 0 0;
}
/* for backward compatbility with pre-font-awesome */
.icon.plus,.icon.arrowleft,.icon.download,.icon.trash,.icon.pen,.icon.arrowright,.icon.magnifier {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon.plus:before { content: "\f067";}
.icon.arrowleft:before { content: "\f060";}
.icon.download:before { content: "\f019";}
.icon.trash:before { content: "\f1f8";}
.icon.pen:before { content: "\f040";}
.icon.arrowright:before { content: "\f061";}
.icon.magnifier:before { content: "\f002";}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
/*! Respond.js v1.4.2: min/max-width media query polyfill
* Copyright 2014 Scott Jehl
* Licensed under MIT
* http://j.mp/respondjs */
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b<t.length;b++){var c=t[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!p[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(w(c.styleSheet.rawCssText,e,f),p[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!s||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}x()};y(),c.update=y,c.getEmValue=u,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
-44
View File
@@ -1,44 +0,0 @@
/**
Created and copyrighted by Massimo Di Pierro <massimo.dipierro@gmail.com>
(MIT license)
Example:
<script src="share.js"></script>
**/
jQuery(function(){
var script_source = jQuery('script[src*="share.js"]').attr('src');
var params = function(name,default_value) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(script_source);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '))||default_value;
}
var path = params('static','social');
var url = encodeURIComponent(window.location.href);
var host = window.location.hostname;
var title = escape(jQuery('title').text());
var twit = 'http://twitter.com/home?status='+title+'%20'+url;
var facebook = 'http://www.facebook.com/sharer.php?u='+url;
var gplus = 'https://plus.google.com/share?url='+url;
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+gplus+'" id="gplus" title="Share on Google Plus"><img src="'+path+'/gplus-32.png" alt="Share on Google Plus" width="32" height="32" /></a></div></div>';
// Add the share tool bar.
jQuery('body').append(tbar);
var st = jQuery('#socialdrawer');
st.css({'opacity':'.7','z-index':'3000','background':'#FFF','border':'solid 1px #666','border-width':' 1px 0 0 1px','height':'20px','width':'40px','position':'fixed','bottom':'0','right':'0','padding':'2px 5px','overflow':'hidden','-webkit-border-top-left-radius':' 12px','-moz-border-radius-topleft':' 12px','border-top-left-radius':' 12px','-moz-box-shadow':' -3px -3px 3px rgba(0,0,0,0.5)','-webkit-box-shadow':' -3px -3px 3px rgba(0,0,0,0.5)','box-shadow':' -3px -3px 3px rgba(0,0,0,0.5)'});
jQuery('#socialdrawer a').css({'float':'left','width':'32px','margin':'3px 2px 2px 2px','padding':'0','cursor':'pointer'});
jQuery('#socialdrawer span').css({'float':'left','margin':'2px 3px','text-shadow':' 1px 1px 1px #FFF','color':'#444','font-size':'12px','line-height':'1em'});
jQuery('#socialdrawer img').hide();
// hover
st.click(function(){
jQuery(this).animate({height:'40px', width:'160px', opacity: 0.95}, 300);
jQuery('#socialdrawer img').show();
});
//leave
st.mouseleave(function(){
st.animate({height:'20px', width: '40px', opacity: .7}, 300);
jQuery('#socialdrawer img').hide();
return false;
} );
});
+86 -28
View File
@@ -12,6 +12,10 @@
$.error('web2py.js has already been loaded!');
}
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
var animateIn = 'fadeIn';
// var animateIn = 'slideDown';
String.prototype.reverse = function () {
return this.split('').reverse().join('');
};
@@ -38,8 +42,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 +67,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) {
/*
@@ -148,7 +182,7 @@
},
/* manage errors in forms */
manage_errors: function (target) {
$('div.error', target).hide().slideDown('slow');
$('div.error', target).hide()[animateIn]('slow');
},
after_ajax: function (xhr) {
/* called whenever an ajax request completes */
@@ -168,7 +202,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();
@@ -232,13 +267,17 @@
}
});
/* help preventing double form submission for normal form (not LOADed) */
$(doc).on('submit', 'form', function () {
var submit_button = $(this).find(web2py.formInputClickSelector);
web2py.disableElement(submit_button);
$(doc).on('submit', 'form', function (e) {
var submit_buttons = $(this).find(web2py.formInputClickSelector);
submit_buttons.each(function() {
web2py.disableElement($(this));
})
/* safeguard in case the form doesn't trigger a refresh,
see https://github.com/web2py/web2py/issues/1100 */
setTimeout(function () {
web2py.enableElement(submit_button);
submit_buttons.each(function() {
web2py.enableElement($(this));
});
}, 5000);
});
doc.ajaxSuccess(function (e, xhr) {
@@ -289,7 +328,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 +355,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) {
@@ -562,8 +617,8 @@
flash: function (message, status) {
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
flash.text(message).addClass(status);
if (flash.html()) flash.append('<span id="closeflash"> &times; </span>')[animateIn]();
},
hide_flash: function () {
$('.w2p_flash').fadeOut(0).html('');
@@ -577,7 +632,7 @@
for (var k = 0; k < triggers[id].length; k++) {
var dep = $('#' + triggers[id][k], target);
var tr = $('#' + triggers[id][k] + '__row', target);
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
else tr.hide();
}
};
@@ -667,8 +722,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 +736,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);
});
},
@@ -767,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/
/* compatibility code - end*/
/* compatibility code - end*/
+16 -24
View File
@@ -148,7 +148,7 @@
{{=T.M("(**%.0d MB**)", total['bytes'] / 1048576)}}
{{pass}}
{{else:}}
{{=T.M("**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
{{=T.M("**not available** (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
{{pass}}
</p>
<p>
@@ -176,7 +176,7 @@
{{=T.M("(**%.0d MB**)", ram['bytes'] / 10485576)}}
{{pass}}
{{else:}}
{{=T.M("``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
{{=T.M("``**not available**``:red (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
{{pass}}
</p>
<p>
@@ -205,7 +205,7 @@
{{=T.M("(**%.0d MB**)", disk['bytes'] / 1048576)}}
{{pass}}
{{else:}}
{{=T.M("``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
{{=T.M("``**not available**``:red (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
{{pass}}
</p>
<p>
@@ -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 -26
View File
@@ -1,15 +1,9 @@
{{left_sidebar_enabled,right_sidebar_enabled=False,('message' in globals())}}
{{extend 'layout.html'}}
{{block header}}
<header class="container-fluid background">
<div class="jumbotron text-center">
{{if response.title:}}
<h1>{{=response.title}}
<small>{{=response.subtitle or ''}}</small></h1>
{{pass}}
</div>
</header>
<center style="background-color: #333; color:white; padding:30px">
<h1>/{{=request.application}}/{{=request.controller}}/{{=request.function}}
</center>
{{end}}
{{if 'message' in globals():}}
@@ -27,26 +21,23 @@
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li>
<li>{{=T('You can modify this application and adapt it to your needs')}}</li>
</ol>
<center style="padding:50px">
<a class="btn btn-primary" href="{{=URL('admin','default','index')}}">
<i class="fa fa-cog"></i>
{{=T("admin")}}
</a>
<a class="btn btn-secondary" href="{{=URL('examples','default','index')}}">{{=T("Online examples")}}</a>
<a class="btn btn-secondary" href="http://web2py.com">web2py.com</a>
<a class="btn btn-secondary" href="http://web2py.com/book">{{=T('Documentation')}}</a>
<a class="btn btn-secondary" href="{{=URL('default','api_get_user_email')}}">{{=T('API Example')}}</a>
<a class="btn btn-secondary" href="{{=URL('default','grid/auth_user')}}">{{=T('Grid Example')}}</a>
<a class="btn btn-secondary" href="{{=URL('default','wiki')}}">{{=T('Wiki Example')}}</a>
</center>
{{elif 'content' in globals():}}
{{=content}}
{{else:}}
{{=BEAUTIFY(response._vars)}}
{{pass}}
{{block right_sidebar}}
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title"><a class="btn-block"
href="{{=URL('admin','default','index')}}">
<i class="glyphicon glyphicon-cog"></i>
{{=T("admin")}}
</a></h3></div>
<div class="panel-body">
{{=T("Don't know what to do?")}}
</div>
<ul class="list-group">
<li class="list-group-item">{{=A(T("Online examples"), _href=URL('examples','default','index'))}}</li>
<li class="list-group-item"><a href="http://web2py.com">web2py.com</a></li>
<li class="list-group-item"><a href="http://web2py.com/book">{{=T('Documentation')}}</a></li>
</ul>
</div>
{{end}}
+2 -2
View File
@@ -10,10 +10,10 @@
{{
if request.args(0)=='login':
if not 'register' in auth.settings.actions_disabled:
form.add_button(T('Sign Up'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn btn-default')
form.add_button(T('Sign Up'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn btn-default btn-secondary')
pass
if not 'request_reset_password' in auth.settings.actions_disabled:
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn btn-default')
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn btn-default btn-secondary')
pass
pass
=form
+1 -4
View File
@@ -7,10 +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}}
{{if request.is_local:}}
{{=response.toolbar()}}
{{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}}
+52 -56
View File
@@ -20,85 +20,83 @@
http://google.com/webmasters -->
<meta name="google-site-verification" content="">
<!-- include stylesheets -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap4.css')}}"/>
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables
HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr-2.8.3.min.js')}}"></script>
<!--[if lt IE 9]>
<script src="{{=URL('static','js/respond-1.4.2.min.js')}}"></script>
<![endif]-->
<!-- Favicons -->
{{include 'web2py_ajax.html'}} <!-- this includes jquery.js, calendar.js/.css and web2py.js -->
{{block head}}{{end}}
{{
# using sidebars need to know what sidebar you want to use
mc0 = 'col-md-12'
mc1 = 'col-md-9'
mc2 = 'col-md-6'
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
middle_column = {0: mc0, 1: mc1, 2: mc2}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}}
</head>
<body>
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
<!-- Navbar ======================================= -->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
{{=response.logo or ''}}
</div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-right">
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}}
</ul>
{{if response.menu:}}
{{=MENU(response.menu, _class='nav navbar-nav',li_class='dropdown',ul_class='dropdown-menu')}}
<nav class="navbar navbar-toggleable-md navbar-light bg-faded">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="http://web2py.com">web2py</a>
<div id="navbarNavDropdown" class="collapse navbar-collapse">
<ul class="navbar-nav">
{{for _item in response.menu or []:}}
{{if len(_item)<4 or not _item[3]:}}
<li class="nav-item {{if _item[1]:}}active{{pass}}">
<a class="nav-link" href="{{=_item[2]}}">{{=_item[0]}}</a>
</li>
{{else:}}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="{{=_item[2]}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{=_item[0]}}</a>
<div class="dropdown-menu">
{{for _subitem in _item[3]:}}
<a class="dropdown-item" href="{{=_subitem[2]}}">{{=_subitem[0]}}</a>
{{pass}}
</div>
</li>
{{pass}}
</div>
{{pass}}
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="text" placeholder="Search">
</form>
{{if 'auth' in globals():}}
<ul class="navbar-nav navbar-right">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{if auth.user:}}{{=auth.user.first_name}}{{else:}}LOGIN{{pass}}
</a>
<div class="dropdown-menu">
{{if auth.user:}}
<a class="dropdown-item" href="{{=URL('default','user/profile')}}">{{=T('Profile')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/change_password')}}">{{=T('Change Password')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/logout')}}">{{=T('Logout')}}</a>
{{else:}}
<a class="dropdown-item" href="{{=URL('default','user/login')}}">{{=T('Login')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/register')}}">{{=T('Sign up')}}</a>
<a class="dropdown-item" href="{{=URL('default','user/retrieve_password')}}">{{=T('Lost Password')}}</a>
{{pass}}
</div>
</li>
</ul>
{{pass}}
</div>
</nav>
<!-- Masthead ===================================== -->
{{block header}}
{{end}}
<!-- Main ========================================= -->
<!-- Begin page content -->
<div class="container-fluid main-container">
{{if left_sidebar_enabled:}}
<div class="col-md-3 left-sidebar">
{{block left_sidebar}}
<h3>Left Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
<div class="{{=middle_column}}">
<div class="col-md-12">
{{block center}}
{{include}}
{{end}}
{{=response.toolbar() if response.show_toolbar else ''}}
</div>
{{if right_sidebar_enabled:}}
<div class="col-md-3">
{{block right_sidebar}}
<h3>Right Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
</div>
{{block footer}} <!-- this is default footer -->
@@ -114,7 +112,7 @@
{{end}}
<!-- The javascript =============================== -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script src="{{=URL('static','js/web2py-bootstrap3.js')}}"></script>
<script src="{{=URL('static','js/web2py-bootstrap4.js')}}"></script>
{{block page_js}}{{end page_js}}
{{if response.google_analytics_id:}}
<!-- Analytics ==================================== -->
@@ -125,7 +123,5 @@
});
</script>
{{pass}}
<!-- Share ============================y============ -->
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script>
</body>
</html>
+4 -1
View File
@@ -10,6 +10,10 @@ environment:
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64"
- PYTHON: "C:/Python36"
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
PYTHON_ARCH: "64"
clone_depth: 50
init:
@@ -20,7 +24,6 @@ install:
- python -m ensurepip
- pip install codecov
- git submodule update --init --recursive
- pip install pycrypto
# Check that we have the expected version and architecture for Python
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
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():
+9 -5
View File
@@ -26,14 +26,16 @@ if PY2:
from email.MIMEText import MIMEText
from email.Charset import add_charset, QP as charset_QP
from urllib import FancyURLopener, urlencode, urlopen
from urllib import quote as urllib_quote, unquote as urllib_unquote
from urllib import quote as urllib_quote, unquote as urllib_unquote, quote_plus as urllib_quote_plus
from string import maketrans
from types import ClassType
import cgi
import cookielib
from xmlrpclib import ProtocolError
from gluon.contrib import ipaddress
BytesIO = StringIO
reduce = reduce
reload = reload
hashlib_md5 = hashlib.md5
iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues()
@@ -62,7 +64,7 @@ if PY2:
return None
if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj)
if isinstance(obj, unicode):
if hasattr(obj, 'encode'):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
@@ -76,6 +78,7 @@ else:
import pickle
from io import StringIO, BytesIO
import copyreg
from importlib import reload
from functools import reduce
from html.parser import HTMLParser
from http import cookies as Cookie
@@ -93,10 +96,11 @@ else:
from email.header import Header
from email.charset import Charset, add_charset, QP as charset_QP
from urllib.request import FancyURLopener, urlopen
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode, quote_plus as urllib_quote_plus
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())
@@ -120,7 +124,7 @@ else:
return None
if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj)
if isinstance(obj, str):
if hasattr(obj, 'encode'):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
@@ -149,7 +153,7 @@ def with_metaclass(meta, *bases):
def to_unicode(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if not isinstance(obj, bytes):
if not hasattr(obj, 'decode'):
return text_type(obj)
return obj.decode(charset, errors)
+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'):
+1055
View File
File diff suppressed because it is too large Load Diff
+59 -44
View File
@@ -18,7 +18,7 @@ import fnmatch
import os
import copy
import random
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long, reload
from gluon.storage import Storage, List
from gluon.template import parse_template
from gluon.restricted import restricted, compile2
@@ -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:
@@ -204,14 +205,15 @@ def LOAD(c=None, f='index', args=None, vars=None,
other_response = Response()
other_request.env.path_info = '/' + \
'/'.join([request.application, c, f] +
map(str, other_request.args))
[str(a) for a in other_request.args])
other_request.env.query_string = \
vars and URL(vars=vars).split('?')[1] or ''
other_request.env.http_web2py_component_location = \
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
@@ -286,7 +288,7 @@ class LoadFactory(object):
other_response = globals.Response()
other_request.env.path_info = '/' + \
'/'.join([request.application, c, f] +
map(str, other_request.args))
[str(a) for a in other_request.args])
other_request.env.query_string = \
vars and html.URL(vars=vars).split('?')[1] or ''
other_request.env.http_web2py_component_location = \
@@ -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)
@@ -666,8 +676,9 @@ def run_view_in(environment):
badv = 'invalid view (%s)' % view
patterns = response.get('generic_patterns')
layer = None
scode = None
if patterns:
regex = re_compile('|'.join(map(fnmatch.translate, patterns)))
regex = re_compile('|'.join(fnmatch.translate(p) for p in patterns))
short_action = '%(controller)s/%(function)s.%(extension)s' % request
allow_generic = regex.search(short_action)
else:
@@ -678,7 +689,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]))
@@ -698,23 +709,27 @@ def run_view_in(environment):
ccode = getcfs(compiled, compiled, lambda: read_pyc(compiled))
layer = compiled
break
if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
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)
restricted(ccode, environment, layer=layer)
# if the view is not compiled
if not layer:
if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view)
if not os.path.exists(filename):
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
# Parse template
scode = parse_template(view,
pjoin(folder, 'views'),
context=environment)
# Compile template
ccode = compile2(scode, filename)
layer = filename
restricted(ccode, environment, layer=layer, scode=scode)
# 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
+12 -1
View File
@@ -49,7 +49,8 @@ class CasAuth(object):
email=lambda v: v.get('email', None),
user_id=lambda v: v['user']),
casversion=1,
casusername='cas:user'
casusername='cas:user',
change_password_url=None
):
self.urlbase = urlbase
self.cas_login_url = "%s/%s" % (self.urlbase, actions[0])
@@ -64,6 +65,9 @@ class CasAuth(object):
#vars=current.request.vars,
scheme=True)
# URL to let users change their password in the IDP system
self.cas_change_password_url = change_password_url
def login_url(self, next="/"):
current.session.token = self._CAS_login()
return next
@@ -74,6 +78,10 @@ class CasAuth(object):
self._CAS_logout()
return next
def change_password_url(self, next="/"):
self._CAS_change_password()
return next
def get_user(self):
user = current.session.token
if user:
@@ -135,3 +143,6 @@ class CasAuth(object):
redirects to the CAS logout page
"""
redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url))
def _CAS_change_password(self):
redirect(self.cas_change_password_url)
@@ -78,10 +78,13 @@ class RPXAccount(object):
def get_user(self):
request = self.request
if request.vars.token:
# Janrain now sends the token via both a POST body and the query
# string, so we should keep only one of these.
token = request.post_vars.token or request.get_vars.token
if token:
user = Storage()
data = urllib.urlencode(
dict(apiKey=self.api_key, token=request.vars.token))
dict(apiKey=self.api_key, token=token))
auth_info_json = fetch(self.auth_url + '?' + data)
auth_info = json.loads(auth_info_json)
+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
#
+7 -5
View File
@@ -13,12 +13,11 @@
import os
import re
import urllib
from gluon import *
from gluon.tools import fetch
from gluon.storage import Storage
import json
from gluon._compat import urlencode
class RPXAccount(object):
@@ -78,10 +77,13 @@ class RPXAccount(object):
def get_user(self):
request = self.request
if request.vars.token:
# Janrain now sends the token via both a POST body and the query
# string, so we should keep only one of these.
token = request.post_vars.token or request.get_vars.token
if token:
user = Storage()
data = urllib.urlencode(
dict(apiKey=self.api_key, token=request.vars.token))
data = urlencode(
dict(apiKey=self.api_key, token=token))
auth_info_json = fetch(self.auth_url + '?' + data)
auth_info = json.loads(auth_info_json)
+32 -6
View File
@@ -104,11 +104,12 @@ def obj2dict(obj, processed=None):
types.BuiltinFunctionType,
types.BuiltinMethodType))
def saml2_handler(session, request, config_filename = None):
def saml2_handler(session, request, config_filename = None, entityid = None):
config_filename = config_filename or os.path.join(request.folder,'private','sp_conf')
client = Saml2Client(config_file = config_filename)
idps = client.metadata.with_descriptor("idpsso")
entityid = idps.keys()[0]
if not entityid:
idps = client.metadata.with_descriptor("idpsso")
entityid = idps.keys()[0]
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
binding, destination = client.pick_binding(
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
@@ -119,7 +120,7 @@ def saml2_handler(session, request, config_filename = None):
if not request.vars.SAMLResponse:
req_id, req = client.create_authn_request(destination, binding=binding)
relay_state = web2py_uuid().replace('-','')
session.saml_outstanding_queries = {req_id: request.url}
session.saml_outstanding_queries = {req_id: request.url}
session.saml_req_id = req_id
http_args = client.apply_binding(binding, str(req), destination,
relay_state=relay_state)
@@ -145,12 +146,21 @@ class Saml2Auth(object):
username=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
email=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
user_id=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
)):
), logout_url=None, change_password_url=None, entityid=None):
self.config_file = config_file
self.maps = maps
# URL for redirecting users to when they sign out
self.saml_logout_url = logout_url
# URL to let users change their password in the IDP system
self.saml_change_password_url = change_password_url
# URL to specify an IDP if using federation metadata or an MDQ
self.entityid = entityid
def login_url(self, next="/"):
d = saml2_handler(current.session, current.request)
d = saml2_handler(current.session, current.request, entityid=self.entityid)
if 'url' in d:
redirect(d['url'])
elif 'error' in d:
@@ -170,6 +180,12 @@ class Saml2Auth(object):
def logout_url(self, next="/"):
current.session.saml2_info = None
current.session.auth = None
self._SAML_logout()
return next
def change_password_url(self, next="/"):
self._SAML_change_password()
return next
def get_user(self):
@@ -180,3 +196,13 @@ class Saml2Auth(object):
d[key] = self.maps[key](user)
return d
return None
def _SAML_logout(self):
"""
exposed SAML.logout()
redirects to the SAML logout page
"""
redirect(self.saml_logout_url)
def _SAML_change_password(self):
redirect(self.saml_change_password_url)
+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:
+2
View File
@@ -137,6 +137,8 @@ def populate_generator(table, default=True, compute=False, contents={}):
continue
elif field.type == 'upload':
continue
elif field.compute is not None:
continue
elif default and not field.default in (None, ''):
record[fieldname] = field.default
elif compute and field.compute:
+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)
+20
View File
@@ -673,3 +673,23 @@ def simple_detect(agent):
if os_version:
os = " ".join((os, os_version))
return os, browser
class mobilize(object):
"""
Decorator for controller functions so they use different views for mobile devices.
WARNING: If you update httpagentparser make sure to leave mobilize for
backwards compatibility.
"""
def __init__(self, func):
self.func = func
def __call__(self):
from gluon import current
user_agent = current.request.user_agent()
if user_agent.is_mobile:
items = current.response.view.split('.')
items.insert(-1, 'mobile')
current.response.view = '.'.join(items)
return self.func()
+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
+3 -25
View File
@@ -94,32 +94,10 @@ import optparse
import time
import sys
import gluon.utils
if (sys.version_info[0] == 2):
from urllib import urlencode, urlopen
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj)
if isinstance(obj, unicode):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
else:
from urllib.request import urlopen
from urllib.parse import urlencode
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj)
if isinstance(obj, str):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
from gluon._compat import to_native, to_bytes, urlencode, urlopen
listeners, names, tokens = {}, {}, {}
def websocket_send(url, message, hmac_key=None, group='default'):
sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
params = urlencode(
@@ -138,8 +116,8 @@ class PostHandler(tornado.web.RequestHandler):
if hmac_key and not 'signature' in self.request.arguments:
self.send_error(401)
if 'message' in self.request.arguments:
message = self.request.arguments['message'][0]
group = self.request.arguments.get('group', ['default'])[0]
message = self.request.arguments['message'][0].decode(encoding='UTF-8')
group = self.request.arguments.get('group', ['default'])[0].decode(encoding='UTF-8')
print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
if hmac_key:
signature = self.request.arguments['signature'][0]
+3 -3
View File
@@ -8,7 +8,7 @@
Support for smart import syntax for web2py applications
-------------------------------------------------------
"""
from gluon._compat import builtin, unicodeT, PY2, to_native
from gluon._compat import builtin, unicodeT, PY2, to_native, reload
import os
import sys
import threading
@@ -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
+62 -37
View File
@@ -14,6 +14,12 @@ 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 pydal.migrator import Migrator, InDBMigrator
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):
"""
@@ -26,7 +32,11 @@ def _default_validators(db, field):
field_type, field_length = field.type, field.length
requires = []
if field_type in (('string', 'text', 'password')):
if isinstance(field.options, list) and field.requires:
requires = validators.IS_IN_SET(field.options, multiple=field_type.startswith('list:'))
elif field.regex and not field.requires:
requires.append(validators.IS_REGEX(regex))
elif field_type in (('string', 'text', 'password')):
requires.append(validators.IS_LENGTH(field_length))
elif field_type == 'json':
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
@@ -44,48 +54,64 @@ def _default_validators(db, field):
requires.append(validators.IS_TIME())
elif field_type == 'datetime':
requires.append(validators.IS_DATETIME())
elif db and field_type.startswith('reference') and \
field_type.find('.') < 0 and \
field_type[10:] in db.tables:
referenced = db[field_type[10:]]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced._id,
referenced._format)
if field.unique:
requires._and = validators.IS_NOT_IN_DB(db, field)
if field.tablename == field_type[10:]:
return validators.IS_EMPTY_OR(requires)
return requires
elif db and field_type.startswith('list:reference') and \
field_type.find('.') < 0 and \
field_type[15:] in db.tables:
referenced = db[field_type[15:]]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced._id,
referenced._format, multiple=True)
else:
requires = validators.IS_IN_DB(db, referenced._id,
multiple=True)
elif db and field_type.startswith('reference'):
if field_type.find('.') < 0 and field_type[10:] in db.tables:
referenced = db[field_type[10:]]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced._id,referenced._format)
else:
requires = validators.IS_IN_DB(db, referenced._id)
elif field_type.find('.') > 0 and field_type[10:].split('.')[0] in db.tables:
table_field = field_type[10:].split('.')
table_name=table_field[0]
field_name=table_field[1]
referenced = db[table_name]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced[field_name],referenced._format)
else:
requires = validators.IS_IN_DB(db, referenced[field_name])
if field.unique:
requires._and = validators.IS_NOT_IN_DB(db, field)
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
return requires
elif db and field_type.startswith('list:reference'):
if field_type.find('.') < 0 and field_type[15:] in db.tables:
referenced = db[field_type[15:]]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced._id,
referenced._format, multiple=True)
else:
requires = validators.IS_IN_DB(db, referenced._id,
multiple=True)
elif field_type.find('.') > 0 and field_type[15:].split('.')[0] in db.tables:
table_field = field_type[15:].split('.')
table_name=table_field[0]
field_name=table_field[1]
referenced = db[table_name]
if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db, referenced[field_name],
referenced._format, multiple=True)
else:
requires = validators.IS_IN_DB(db, referenced[field_name],
multiple=True)
if field.unique:
requires._and = validators.IS_NOT_IN_DB(db, field)
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
return requires
# does not get here for reference and list:reference
if field.unique:
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
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)
if isinstance(requires, list):
if field.unique:
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
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:
null = null='' if field.type in ('string', 'text', 'password') else None
requires[0] = validators.IS_EMPTY_OR(requires[0], null=null)
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 +122,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(
+100 -53
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, urllib_quote
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:
@@ -215,7 +220,12 @@ class Request(Storage):
if is_json:
try:
json_vars = json_parser.load(body)
# In Python 3 versions prior to 3.6 load doesn't accept bytes and
# bytearray, so we read the body convert to native and use loads
# instead of load.
# This line can be simplified to json_vars = json_parser.load(body)
# if and when we drop support for python versions under 3.6
json_vars = json_parser.loads(to_native(body.read()))
except:
# incoherent request bodies can still be parsed "ad-hoc"
json_vars = {}
@@ -227,8 +237,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:
@@ -327,11 +336,16 @@ class Request(Storage):
user_agent = session._user_agent
if user_agent:
return user_agent
user_agent = user_agent_parser.detect(self.env.http_user_agent)
http_user_agent = self.env.http_user_agent or ''
user_agent = user_agent_parser.detect(http_user_agent)
for key, value in user_agent.items():
if isinstance(value, dict):
user_agent[key] = Storage(value)
user_agent = session._user_agent = Storage(user_agent)
user_agent = Storage(user_agent)
user_agent.is_mobile = 'Mobile' in http_user_agent
user_agent.is_tablet = 'Tablet' in http_user_agent
session._user_agent = user_agent
return user_agent
def requires_https(self):
@@ -349,14 +363,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 +429,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 +464,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"' % (to_native(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
@@ -465,46 +478,67 @@ class Response(Storage):
response.cache_includes = (cache_method, time_expire).
Example: (cache.disk, 60) # caches to disk for 1 minute.
"""
app = current.request.application
# We start by building a files list in which adjacent files internal to
# the application are placed in a list inside the files list.
#
# We will only minify and concat adjacent internal files as there's
# no way to know if changing the order with which the files are apppended
# will break things since the order matters in both CSS and JS and
# internal files may be interleaved with external ones.
files = []
ext_files = []
has_js = has_css = False
# For the adjacent list we're going to use storage List to both distinguish
# from the regular list and so we can add attributes
internal = List()
internal.has_js = False
internal.has_css = False
done = set() # to remove duplicates
for item in self.files:
if isinstance(item, (list, tuple)):
ext_files.append(item)
if not isinstance(item, list):
if item in done:
continue
done.add(item)
if isinstance(item, (list, tuple)) or not item.startswith('/' + app): # also consider items in other web2py applications to be external
if internal:
files.append(internal)
internal = List()
internal.has_js = False
internal.has_css = False
files.append(item)
continue
if extensions and not item.rpartition('.')[2] in extensions:
continue
if item in files:
continue
internal.append(item)
if item.endswith('.js'):
has_js = True
internal.has_js = True
if item.endswith('.css'):
has_css = True
files.append(item)
internal.has_css = True
if internal:
files.append(internal)
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
# cache for 5 minutes by default
key = hashlib.md5(repr(files)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60 * 5)
def call_minify(files=files):
return minify.minify(files,
URL('static', 'temp'),
current.request.folder,
self.optimize_css,
self.optimize_js)
if cache:
cache_model, time_expire = cache
files = cache_model('response.files.minified/' + key,
call_minify,
time_expire)
else:
files = call_minify()
files.extend(ext_files)
s = []
for item in files:
# We're done we can now minify
if have_minify:
for i, f in enumerate(files):
if isinstance(f, List) and ((self.optimize_css and f.has_css) or (self.optimize_js and f.has_js)):
# cache for 5 minutes by default
key = hashlib_md5(repr(f)).hexdigest()
cache = self.cache_includes or (current.cache.ram, 60 * 5)
def call_minify(files=f):
return List(minify.minify(files,
URL('static', 'temp'),
current.request.folder,
self.optimize_css,
self.optimize_js))
if cache:
cache_model, time_expire = cache
files[i] = cache_model('response.files.minified/' + key,
call_minify,
time_expire)
else:
files[i] = call_minify()
def static_map(s, item):
if isinstance(item, str):
f = item.lower().split('?')[0]
ext = f.rpartition('.')[2]
@@ -523,6 +557,14 @@ class Response(Storage):
tmpl = template_mapping.get(f)
if tmpl:
s.append(tmpl % item[1])
s = []
for item in files:
if isinstance(item, List):
for f in item:
static_map(s, f)
else:
static_map(s, item)
self.write(''.join(s), escape=False)
def stream(self,
@@ -578,9 +620,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)
@@ -638,6 +680,11 @@ class Response(Storage):
if download_filename is None:
download_filename = filename
if attachment:
# Browsers still don't have a simple uniform way to have non ascii
# characters in the filename so for now we are percent encoding it
if isinstance(download_filename, unicodeT):
download_filename = download_filename.encode('utf-8')
download_filename = urllib_quote(download_filename)
headers['Content-Disposition'] = \
'attachment; filename="%s"' % download_filename.replace('"', '\"')
return self.stream(stream, chunk_size=chunk_size, request=request)
@@ -1022,7 +1069,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 +1240,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>')
+39 -48
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):
@@ -1416,20 +1419,21 @@ class LINK(DIV):
class SCRIPT(DIV):
tag = 'script'
tag = b'script'
tagname = to_bytes(tag)
def xml(self):
(fa, co) = self._xml()
fa = to_native(fa)
fa = to_bytes(fa)
# no escaping of subcomponents
co = '\n'.join([str(component) for component in
co = b'\n'.join([to_bytes(component) for component in
self.components])
if co:
# <script [attributes]><!--//--><![CDATA[//><!--
# script body
# //--><!]]></script>
# return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
return b'<%s%s><!--\n%s\n//--></%s>' % (self.tagname, fa, co, self.tagname)
else:
return DIV.xml(self)
@@ -1437,18 +1441,19 @@ class SCRIPT(DIV):
class STYLE(DIV):
tag = 'style'
tagname = to_bytes(tag)
def xml(self):
(fa, co) = self._xml()
fa = to_native(fa)
fa = to_bytes(fa)
# no escaping of subcomponents
co = '\n'.join([str(component) for component in
co = b'\n'.join([to_bytes(component) for component in
self.components])
if co:
# <style [attributes]><!--/*--><![CDATA[/*><!--*/
# style body
# /*]]>*/--></style>
return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
return b'<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tagname, fa, co, self.tagname)
else:
return DIV.xml(self)
@@ -1862,7 +1867,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 +1917,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 +1985,6 @@ class OPTGROUP(DIV):
class SELECT(INPUT):
"""
Examples:
@@ -2015,7 +2019,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 +2029,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 +2380,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.
@@ -2430,7 +2433,7 @@ class BEAUTIFY(DIV):
if level == 0:
return
for c in self.components:
if hasattr(c, 'value') and not callable(c.value):
if hasattr(c, 'value') and not callable(c.value) and not isinstance(c, cgi.FieldStorage):
if c.value:
components.append(c.value)
if hasattr(c, 'xml') and callable(c.xml):
@@ -2548,7 +2551,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 +2565,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 +2658,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 +2698,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 +2707,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):
+11 -4
View File
@@ -11,7 +11,7 @@ HTTP statuses helpers
"""
import re
from gluon._compat import iteritems
from gluon._compat import iteritems, unicodeT, to_bytes
__all__ = ['HTTP', 'redirect']
@@ -111,22 +111,29 @@ class HTTP(Exception):
if not body:
body = status
if isinstance(body, (str, bytes, bytearray)):
if isinstance(body, unicodeT):
body = to_bytes(body) # This must be done before len
headers['Content-Length'] = len(body)
rheaders = []
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 = to_bytes(body)
return [body]
elif hasattr(body, '__iter__'):
return body
else:
return [str(body)]
body = str(body)
if isinstance(body, unicodeT):
body = to_bytes(body)
return [body]
@property
def message(self):
@@ -148,7 +155,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)))
+35 -16
View File
@@ -18,10 +18,11 @@ import pkgutil
import logging
from cgi import escape
from threading import RLock
from gluon.utf8 import Utf8
from gluon.utils import local_html_escape
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
from pydal.contrib.portalocker import read_locked, LockedFile
from gluon.fileutils import listdir
@@ -49,8 +50,10 @@ DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word
if PY2:
NUMBERS = (int, long, float)
from gluon.utf8 import Utf8
else:
NUMBERS = (int, float)
Utf8 = str
# pattern to find T(blah blah blah) expressions
PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
@@ -107,15 +110,17 @@ def markmin(s):
def upper_fun(s):
return unicode(s, 'utf-8').upper().encode('utf-8')
return to_unicode(s).upper()
def title_fun(s):
return unicode(s, 'utf-8').title().encode('utf-8')
return to_unicode(s).title()
def cap_fun(s):
return unicode(s, 'utf-8').capitalize().encode('utf-8')
return to_unicode(s).capitalize()
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
@@ -426,10 +431,16 @@ class lazyT(object):
return str(self) if self.M else local_html_escape(str(self), quote=False)
def encode(self, *a, **b):
return str(self).encode(*a, **b)
if PY2 and a[0] != 'utf8':
return to_unicode(str(self)).encode(*a, **b)
else:
return str(self)
def decode(self, *a, **b):
return str(self).decode(*a, **b)
if PY2:
return str(self).decode(*a, **b)
else:
return str(self)
def read(self):
return str(self)
@@ -951,32 +962,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)
+29 -29
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")
@@ -182,12 +183,13 @@ def serve_controller(request, response, session):
response._view_environment.update(page)
page = run_view_in(response._view_environment)
# logic to garbage collect after exec, not always, once every 100 requests
global requests
requests = ('requests' in globals()) and (requests + 1) % 100 or 0
if not requests:
gc.collect()
# end garbage collection logic
if not request.env.web2py_disable_garbage_collect:
# logic to garbage collect after exec, not always, once every 100 requests
global requests
requests = ('requests' in globals()) and (requests + 1) % 100 or 0
if not requests:
gc.collect()
# end garbage collection logic
# ##################################################
# set default headers it not set
@@ -230,7 +232,7 @@ class LazyWSGI(object):
to call third party WSGI applications
"""
self.response.status = str(status).split(' ', 1)[0]
self.response.status = int(str(status).split(' ', 1)[0])
self.response.headers = dict(headers)
return lambda *args, **kargs: \
self.response.write(escape=False, *args, **kargs)
@@ -254,6 +256,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 +297,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 +328,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 +358,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 +390,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 +406,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 +625,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:
@@ -746,7 +746,7 @@ class HttpServer(object):
sock_list = [ip, port]
if not ssl_certificate or not ssl_private_key:
logger.info('SSL is off')
elif not rocket.ssl:
elif not rocket.has_ssl:
logger.warning('Python "ssl" module unavailable. SSL is OFF')
elif not exists(ssl_certificate):
logger.warning('unable to open SSL certificate. SSL is OFF')
+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
+12 -7
View File
@@ -10,7 +10,7 @@ Restricted environment to execute application's code
"""
import sys
from gluon._compat import pickle, ClassType
from gluon._compat import pickle, ClassType, unicodeT, to_bytes
import traceback
import types
import os
@@ -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:
@@ -189,10 +192,10 @@ class RestrictedError(Exception):
# safely show an useful message to the user
try:
output = self.output
if isinstance(output, unicode):
output = output.encode("utf8")
elif not isinstance(output, str):
if not isinstance(output, str, bytes, bytearray):
output = str(output)
if isinstance(output, unicodeT):
output = to_bytes(output)
except:
output = ""
return output
@@ -202,7 +205,7 @@ def compile2(code, layer):
return compile(code, layer, 'exec')
def restricted(ccode, environment=None, layer='Unknown'):
def restricted(ccode, environment=None, layer='Unknown', scode=None):
"""
Runs code in environment and returns the output. If an exception occurs
in code it raises a RestrictedError containing the traceback. Layer is
@@ -227,7 +230,9 @@ def restricted(ccode, environment=None, layer='Unknown'):
sys.excepthook(etype, evalue, tb)
del tb
output = "%s %s" % (etype, evalue)
raise RestrictedError(layer, ccode, output, environment)
# Save source code in ticket when available
scode = scode if scode else ccode
raise RestrictedError(layer, scode, output, environment)
def snapshot(info=None, context=5, code=None, environment=None):
+7 -9
View File
@@ -22,12 +22,11 @@ import re
import logging
import traceback
import threading
import urllib
from gluon.storage import Storage, List
from gluon.http import HTTP
from gluon.fileutils import abspath, read_file
from gluon.settings import global_settings
from gluon._compat import urllib_unquote, urllib_quote, iteritems, xrange
from gluon._compat import urllib_unquote, urllib_quote, iteritems, xrange, urllib_quote_plus
isdir = os.path.isdir
isfile = os.path.isfile
@@ -206,8 +205,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)
@@ -236,7 +234,7 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
path_info, query_string = uri, ''
query_string += \
'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
(status, ticket, urllib.quote_plus(
(status, ticket, urllib_quote_plus(
request.env.request_uri), request.url)
if uri.startswith('http://') or uri.startswith('https://'):
# make up a response
@@ -271,12 +269,12 @@ def try_redirect_on_error(http_object, request, ticket=None):
elif '?' in redir:
url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
(redir, status, ticket,
urllib.quote_plus(request.env.request_uri),
urllib_quote_plus(request.env.request_uri),
request.url)
else:
url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
(redir, status, ticket,
urllib.quote_plus(request.env.request_uri),
urllib_quote_plus(request.env.request_uri),
request.url)
return HTTP(303, 'You are being redirected <a href="%s">here</a>' % url, Location=url)
return http_object
@@ -317,7 +315,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 +1038,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:

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