Compare commits

...

269 Commits

Author SHA1 Message Date
mdipierro
4b0e1856b5 reverted 2a245d36 2015-12-24 09:04:45 -06:00
mdipierro
94841c90c3 R-2.13.3 2015-12-24 08:50:24 -06:00
mdipierro
f14e5f728c fixing mess 2015-12-24 08:48:19 -06:00
mdipierro
8443c17839 sync'ed appadmins 2015-12-24 08:36:16 -06:00
mdipierro
be8114127e Merge pull request #1141 from gi0baro/master
Tracking latest pyDAL changes
2015-12-24 08:30:31 -06:00
gi0baro
4eaef303ff Tracking latest pyDAL changes 2015-12-24 15:21:52 +01:00
mdipierro
319a3fc1dc Merge branch 'BuhtigithuB-fix/no-more-deprecated-has-key' 2015-12-23 23:11:55 -06:00
mdipierro
463d643e2c fmerged 2015-12-23 23:11:34 -06:00
mdipierro
0cbed12952 Merge pull request #1137 from cassiobotaro/fix_logging
fixing logging old behaviour
2015-12-23 23:08:17 -06:00
mdipierro
b5994e57a4 Merge pull request #1136 from cassiobotaro/remove_25_support
update files removing 2.5 things
2015-12-23 23:07:54 -06:00
Cássio Botaro
e239b975be Change to re-run AppVeyor 2015-12-23 11:19:19 -02:00
Richard Vézina
0259ea3d29 no more deprecated .has_key(...) 2015-12-22 15:39:32 -05:00
cassiobotaro
db4c008de3 Minor changes 2015-12-21 15:40:25 -02:00
cassiobotaro
7921e5148a fixing logging old behaviour 2015-12-21 12:10:50 -02:00
cassiobotaro
ee23eab77a update files removing 2.5 things 2015-12-21 11:55:44 -02:00
mdipierro
2344386f77 better docstring for Auth.jwt 2015-12-18 19:19:43 -06:00
mdipierro
b5e12031c5 added Auth(db,jwt=dict(secret_key='secret')) and auth.allows_jwt() before auth.requires_login() 2015-12-18 19:12:41 -06:00
mdipierro
85e6840cf0 Merge branch 'master' of github.com:web2py/web2py 2015-12-18 18:09:09 -06:00
mdipierro
4c3006acb4 Merge pull request #1135 from gi0baro/master
Track latest pyDAL GAE fix by @mdipierro
2015-12-18 18:08:53 -06:00
gi0baro
f8f008cab5 Track latest pyDAL GAE fix by @mdipierro 2015-12-18 12:51:17 +01:00
mdipierro
6bff8af458 CHANGELOG 2015-12-18 04:52:43 -06:00
mdipierro
b67edb083e fixed compileapp problem in appveyor test (2nd attempt) 2015-12-18 04:44:03 -06:00
mdipierro
4125a97ce1 fixed compileapp problem in appveyor test 2015-12-18 04:39:51 -06:00
mdipierro
78cf55bf9a R-2.13.2 2015-12-18 04:28:16 -06:00
mdipierro
931daaff89 fixed security issue in reset password when registration_requires_authorization, thanks Giovanni Verde 2015-12-18 04:11:26 -06:00
mdipierro
c6550f0adc fixed a condition that allows reset_password if a reset link is sent before a user is blocked 2015-12-18 03:40:12 -06:00
mdipierro
22c89d8dcc version 2.13.1 2015-12-17 21:19:08 -06:00
mdipierro
1636528a0f Merge pull request #1132 from gi0baro/master
Tracking pyDAL v15.12
2015-12-17 21:08:30 -06:00
gi0baro
c02229d79c Tracking pyDAL v15.12 2015-12-16 21:55:23 +01:00
mdipierro
acb05dbfe1 added retrieve to fabfile and fixed minor bug 2015-12-15 23:13:46 -06:00
mdipierro
fda1117dd7 fixed import 2015-12-14 15:44:34 -06:00
mdipierro
d1fde23182 create_user spelling fixed 2015-12-14 15:43:50 -06:00
mdipierro
adf4c93860 Merge pull request #1124 from matclab/issue1123
Convert attachments to a list if necessary.
2015-12-14 15:32:07 -06:00
mdipierro
02f1903c3d Merge pull request #1127 from gi0baro/appveyor-skip
Avoid to run pyDAL adapters tests on appveyor
2015-12-14 15:31:23 -06:00
mdipierro
1137027ecc Merge pull request #1126 from nextghost/master
Parse GET vars from rewritten query string, not the original. Fixes #730
2015-12-14 15:31:10 -06:00
mdipierro
229616b9fc added fabfile form deployment and maintenance tasks 2015-12-14 15:16:00 -06:00
gi0baro
a75a8cbf46 Avoid to run pyDAL adapters tests on appveyor 2015-12-13 12:34:54 +01:00
Martin Doucha
9650ff7516 Parse GET vars from rewritten query string, not the original. Fixes #730 2015-12-12 23:59:15 +01:00
Mathieu Clabaut
5b90f3f532 Convert attachments to a list if necessary.
Also corrects a typo that was apparently silenced by the bug.
This closes issue #1123
2015-12-09 14:46:05 +01:00
mdipierro
483092787b Merge pull request #1120 from dokime7/patch-10
Fix oauth2 renew token
2015-12-07 19:58:24 -06:00
Jeremie Dokime
9b17048882 Fix oauth2 renew token
When getting an oauth token, we must use code or refresh_token but not twice.
2015-12-07 11:18:59 +01:00
mdipierro
30fe7400f9 syncing latest mydal 2015-12-04 15:14:02 -06:00
mdipierro
ada9353a7e removed unwanted referene to jwt in tools 2015-12-04 15:10:25 -06:00
mdipierro
b0373297e0 Browser auto-fill can corrupt data while using admin database interface #1045, thanks dkleissa 2015-12-04 12:31:35 -06:00
mdipierro
2dbbef724c DIV.elements fails when searching for an attribute with an integer value #1074 2015-12-04 12:21:05 -06:00
mdipierro
eb7017fd9a fixed auth.settings.register_onaccept is not firing when signing up through third-party #1081 2015-12-04 12:14:39 -06:00
mdipierro
4c039574df skip failed views, thanks Anthony 2015-12-04 12:06:46 -06:00
mdipierro
ab900957fe fixed Cookie parsing stopped after first invalid cookie #1084, thanks paultuckey 2015-12-04 11:44:51 -06:00
mdipierro
f960c8f6df fixed add_membership, del_membership, add_membership = IntegrityError (when auth.enable_record_versioning) #1087 2015-12-04 11:42:06 -06:00
mdipierro
d2910327c0 temp fix for IS_DATETIME with timezone info & record versioning causes TypeError: can't compare offset-naive and offset-aware datetimes #1094 2015-12-04 11:31:55 -06:00
mdipierro
dfd6d52192 fixed missing helper.get(attribute) 2015-12-04 11:28:39 -06:00
mdipierro
7dd8a3c853 fixed Reference to http://..../[app]/default/user/manage_users in welcome's default controller #1102 2015-12-04 11:24:00 -06:00
mdipierro
71ae754fcd fixed #1110, allow passing unicode to template render 2015-12-04 11:16:15 -06:00
mdipierro
0520770a7e fixed #1111, Validator IS_IPV4: options is_private=False not working, thanks Nbushkov 2015-12-04 11:12:44 -06:00
mdipierro
6b880fb455 fixed class concatenation 2015-12-04 10:46:20 -06:00
mdipierro
dd180019a1 Merge pull request #1118 from matclab/readonly-label
Add readonly class to labels of readonly fields and set padding to 0
2015-12-04 10:43:17 -06:00
mdipierro
638f1f902a Merge pull request #1116 from timnyborg/patch-7
Fix IS_NOT_IN_DB to work with custom primarykey
2015-12-04 10:42:25 -06:00
mdipierro
73061e3bf5 Merge pull request #1114 from BuhtigithuB/Improve/ldap-contrib-self-signed-certificate
Improve/ldap contrib self signed certificate
2015-12-04 10:42:05 -06:00
mdipierro
5a18e29c2e Merge pull request #1113 from timnyborg/patch-6
Fix grid count when using groupby on MSSQL
2015-12-04 10:41:36 -06:00
mdipierro
721af77c90 Merge pull request #1108 from dokime7/patch-9
Send client_secret when get oauth2 refresh_token
2015-12-04 10:38:04 -06:00
mdipierro
2cf6797b43 Merge pull request #1106 from erilyth/master
Added XML to retrieve pickled XML data #1067
2015-12-04 10:37:50 -06:00
mdipierro
5c4145743f Merge pull request #1103 from ilvalle/w2p_target
added w2p_target attribute in form to control the output destination
2015-12-04 10:37:14 -06:00
mdipierro
b4733e4617 added gluon/contrib/web2py_jwt.py, thanks Niphlod 2015-12-04 10:30:56 -06:00
mdipierro
b51d217d9b french pluralizaiton rules, thanks Mathieu Clabaut 2015-12-04 10:07:38 -06:00
Mathieu Clabaut
864dbe73f2 Add readonly class to labels of readonly fields and set padding to 0
This allow for readonly fields content to be vertical aligned with their label
2015-12-04 15:29:45 +01:00
Tim Nyborg
b942fc8f7a Fix IS_NOT_IN_DB to work with custom primarykey
Validator currently selects custom id properly, but then explicitly checks .id
2015-12-03 15:02:21 +00:00
Hardirc
b2a65dbba4 Support for self-signed certificate LDAPS implementation 2015-12-02 14:18:09 -05:00
Hardirc
d36d4d77f7 replace .has_key() by in, .has_key() is deprecated 2015-12-02 14:04:51 -05:00
Hardirc
c8db6d5fb7 Improve PEP8 and readability 2015-12-02 14:02:27 -05:00
Tim Nyborg
1b77c2294a Fix grid count when using groupby on MSSQL
Any query like SELECT COUNT(*) from (SELECT COUNT(*) FROM ...) will fail with 'No column name was specified for column 1 of '_tmp'', so I'm providing an alias for the subquery's field
2015-11-30 16:09:53 +00:00
Jeremie Dokime
98a81c9fbd Send client_secret when get oauth2 refresh_token
With some oauth2 providers, the oauth client_secret is required when getting a refresh_token.
2015-11-23 10:10:00 +01:00
Batchu Venkat Vishal
4bf5a70dc0 Added XML to retrieve pickled XML data #1067 2015-11-19 15:01:23 +05:30
ilvalle
d883e3d84e added w2p_target attribute in form to control the output destination 2015-11-14 12:03:20 +01:00
mdipierro
db37cf6a58 Revert "Labels should get their information from the render function of the records."
This reverts commit 65c87386c1.
2015-11-12 18:25:48 -06:00
mdipierro
dba5c97d51 fixed check for form tampering 2015-11-11 18:38:48 -06:00
mdipierro
948bd0c671 fixed check for form tampering 2015-11-11 18:20:37 -06:00
mdipierro
5d8ff8ba2c removed login_once_after_registration 2015-11-11 09:14:05 -06:00
mdipierro
503cd59adc auth.settings.login_once_after_registration 2015-11-11 09:03:54 -06:00
mdipierro
a0bcd2287b Merge branch 'josedesoto-issue/1095' 2015-10-30 23:10:35 -05:00
mdipierro
430163f70b fixed conflict 2015-10-30 23:10:25 -05:00
mdipierro
52b59e9b71 Merge pull request #1092 from niphlod/fix/1090
fixes #1090
2015-10-30 23:08:47 -05:00
mdipierro
e8f87ea274 Merge pull request #1091 from dokime7/patch-8
Add renew token by using refresh_token
2015-10-30 23:08:37 -05:00
mdipierro
fb6fa0c448 Merge pull request #1088 from niphlod/fix/1083
fixes #1083
2015-10-30 23:07:58 -05:00
mdipierro
935c95ccfc Merge pull request #1086 from gi0baro/pydal-tests
Run pydal's tests inside web2py using contrib adapters
2015-10-30 23:07:37 -05:00
mdipierro
e180e69467 fixed a typo, thanks James Burke 2015-10-30 23:06:00 -05:00
engeens
5c9d197f93 issue #1095. Added two-factor authentication methods and onvalidation. Fixed last attempt two-factor retry login
issue #1095. Added return user for two_factor_onvalidation
2015-10-30 15:09:51 +01:00
mdipierro
e417d311e5 added link to http://www.web2pyref.com/ 2015-10-29 20:59:15 -05:00
mdipierro
199f93f262 fixed typo in tools.py, thanks James Burke 2015-10-29 20:56:40 -05:00
niphlod
64a8880c80 fixes #1090
removed timezone for IS_DATE* validators
2015-10-26 09:50:09 +01:00
Jeremie Dokime
257c514bd4 Add renew token by using refresh_token
Handle the refresh_token mechanism to renew the access_token when expire.
2015-10-25 20:48:04 +01:00
niphlod
12f848c899 fixes #1083 2015-10-19 21:50:34 +02:00
mdipierro
4de007a946 fixed possible problem with cache.action 2015-10-16 21:39:30 -05:00
gi0baro
b59a93e24e Run pydal's tests inside web2py using contrib adapters 2015-10-16 14:44:41 +02:00
mdipierro
bbed326c20 fixed commit error 2015-10-07 13:15:39 -05:00
mdipierro
874398c38c Merge pull request #1080 from BuhtigithuB/improve/ldap-auth-more-dry
Make ldap_auth a bit more DRY
2015-10-07 13:12:30 -05:00
mdipierro
a9f8fbadae Merge pull request #1077 from leonelcamara/fix_wiki_extra
Fixes #721
2015-10-07 13:08:16 -05:00
mdipierro
e62320ff9f Merge pull request #1076 from gi0baro/master
Tracking pydal 15.09
2015-10-07 13:07:54 -05:00
mdipierro
b3e606295e committed changelog and removed unwanted file 2015-10-07 13:04:12 -05:00
mdipierro
b8c2bd7303 fixed LazyCrypt and fixed git problem 2015-10-07 13:02:30 -05:00
mdipierro
1387b26606 fixed LazyCrypt, thanks Denes 2015-10-07 12:57:20 -05:00
Richard Vézina
c6a7732d32 Don't update record when values are the same 2015-10-06 14:36:45 -04:00
Richard Vézina
0036d9c45b Make ldap_auth a bit more DRY 2015-10-06 14:30:50 -04:00
Leonel Câmara
b99fb7dedf Fixes #721
Fixes a bug where auth.wiki was not respecting the extra keyword argument
2015-09-29 00:21:01 +01:00
gi0baro
344590470b Tracking pydal 15.09 2015-09-28 15:50:42 +02:00
mdipierro
2c57dc084e Merge pull request #1073 from niphlod/fix/1043
fixes #1043 , thanks @bobstjon
2015-09-27 14:40:27 -05:00
mdipierro
c17ba0a020 Merge pull request #1072 from niphlod/fix/1039
fixes #1039
2015-09-27 14:40:14 -05:00
mdipierro
7d4b460e1b Merge pull request #1071 from niphlod/fix/1068
fixes #1068
2015-09-27 14:39:58 -05:00
mdipierro
6680ea8ab7 Merge pull request #1061 from nklever/master
small changes in sqlhtml.py, validator.py, contrib/spreadsheet.py
2015-09-27 14:39:47 -05:00
niphlod
353db90a64 fixes #1043 , thanks @bobstjon 2015-09-21 22:24:59 +02:00
niphlod
827e663ac4 better list: widget 2015-09-21 22:18:18 +02:00
niphlod
de399691ce fixes #1039
It was a REEEAALLY good catch :-)
2015-09-21 22:09:17 +02:00
niphlod
46f081c45c fixes #1068
threw in also a better list:string widget repr for bs3

Updated also the bootswatch theme because something was wrong
2015-09-21 21:38:28 +02:00
mdipierro
0fa0dbaeea Merge branch 'master' of github.com:web2py/web2py 2015-09-20 14:07:06 -05:00
mdipierro
b47511c896 token default = web2py_uuid 2015-09-20 14:07:01 -05:00
mdipierro
e31318eaa8 Merge pull request #1066 from lraphael/master
fix unindented lines
2015-09-18 00:41:34 -05:00
mdipierro
72ee538883 Merge pull request #1065 from leonelcamara/pa_integration
PythonAnywhere integration
2015-09-18 00:41:20 -05:00
mdipierro
b6ddc6098e Merge pull request #1060 from viniciusban/1059-response-render-uses-original-response-view
Closes #1059: get `response.view` from the environment
2015-09-18 00:40:04 -05:00
mdipierro
90854eae44 Merge pull request #1058 from niphlod/fix/735
fixes issue #735
2015-09-18 00:39:08 -05:00
mdipierro
2bceb3f95f Merge pull request #1057 from niphlod/fix/823
fixes #823
2015-09-18 00:38:55 -05:00
mdipierro
9da1e29014 Merge pull request #1056 from niphlod/fix/wiki_typo
fixes typo in wiki.
2015-09-18 00:38:38 -05:00
Raphael Lechner
39ba9dc1a9 fix unindent lines 2015-09-16 17:12:45 +02:00
Leonel Câmara
36db9719ef Deal with the corner case of already created accounts
Polished everything a bit
2015-09-15 17:24:57 +01:00
Leonel Câmara
125cbd93a0 Allow deploying to pythonanywhere from the web2py admin that you're running locally. 2015-09-08 00:51:09 +01:00
Nik Klever
bc267ce17b Added Column- and Row-Headers to be more flexible with the headers of the
spreadsheet.

Added also a boolean "select" parameter for the sheet.cell function which
allows to use a HTML select tag instead of an input tag for this cell.
2015-09-05 15:01:02 +02:00
Nik Klever
65c87386c1 Labels should get their information from the render function of the records. 2015-09-05 14:57:55 +02:00
Nik Klever
2a245d36f4 If in any of the form fields are unicode strings entered as input, the
unicode characters in these strings are lost in self.vars.

This conditions sets it back to the original input.

Might be, that this should be done at another place, but it works.
2015-09-05 14:38:32 +02:00
viniciusban
dcf64a661d Closes #1059: get response.view from the environment 2015-09-03 20:39:37 -03:00
niphlod
1c74afc01b fixes issue #735 2015-09-03 18:33:54 +02:00
niphlod
5dbcda9f38 fixes #823 2015-09-03 18:08:33 +02:00
niphlod
ac02d52f05 fixes typo in wiki. As usual, lack of unittests made this possible.
We should really make each developer "adopt" a piece of web2py to test
and care if we don't want to write unittests.
2015-09-03 17:56:45 +02:00
mdipierro
d4270373e1 fixed bug in redirect to cas service, thanks Fernando González 2015-09-01 23:07:18 -05:00
mdipierro
e4b27080ca Merge pull request #1051 from ShySec/master
added HttpOnly cookies (default)
2015-08-30 20:41:23 -05:00
mdipierro
692791a518 Merge pull request #1053 from BuhtigithuB/feature/redirect-next-var-when-logged-on-page-reload
No credentials request if logged in and URL contains user/login?_next=
2015-08-30 00:58:27 -05:00
mdipierro
9190191c7a changed the default BS3 theme 2015-08-23 10:07:32 -05:00
mdipierro
7bd8f6a1a9 Merge pull request #1054 from BuhtigithuB/Improve/PEP8-gluon-tools-py
Improve PEP8 gluon/tools.py
2015-08-21 00:07:06 -05:00
mdipierro
64e115f442 Merge pull request #1050 from timnyborg/patch-5
Update simplejsonrpc.py - move default to method signature
2015-08-21 00:03:24 -05:00
Richard Vézina
61f685d225 Improve PEP8 gluon/tools.py 2015-08-20 17:16:13 -04:00
Richard Vézina
c56fc2f6a0 Improve proposed enhancement #1052 2015-08-20 15:23:59 -04:00
mdipierro
bb2aa29867 fixed google link to viewer in autolinks.py 2015-08-20 14:00:21 -05:00
Richard Vézina
08b6832809 No credentials request if logged in and URL contains user/login?_next= 2015-08-19 14:47:21 -04:00
kelson
cbbd1246db re import required for assertRegexpMatches port 2015-08-19 11:33:47 -04:00
kelson
0a79bf3afd assertRegexpMatches requires port for python2.5 and python2.6 2015-08-19 11:30:07 -04:00
kelson
db5e58e49f added HttpOnly cookies (default)
added unit tests for cookie layout, secure cookies, and HttpOnly cookies
Session.httponly_cookies=False to revert HttpOnly cookies
2015-08-19 11:25:00 -04:00
Tim Nyborg
5030d3144f Update simplejsonrpc.py
Default best placed in method signature, 
Thanks to cassiobotaro for pointing it out
2015-08-19 11:59:40 +01:00
mdipierro
edcc2e44dc R-2.12.3 2015-08-18 19:15:01 -05:00
mdipierro
2cf9f26b0d fixed pydal tracking again 2015-08-18 15:13:57 -05:00
mdipierro
4b99b6fdd7 adding debounced ajax calls 2015-08-18 14:16:10 -05:00
mdipierro
622430583f Merge pull request #1047 from web2py/revert-1046-master
Revert "added default HttpOnly cookies"
2015-08-18 13:59:07 -05:00
mdipierro
89cc5a5f70 Revert "added default HttpOnly cookies" 2015-08-18 13:57:44 -05:00
mdipierro
6899154fcd reverted DAL to v15.07 2015-08-18 13:46:38 -05:00
mdipierro
47c0e461f1 Merge pull request #1046 from ShySec/master
added default HttpOnly cookies
2015-08-18 11:53:54 -05:00
mdipierro
93237837ed Merge pull request #1042 from cassiobotaro/master
Allow change quality when RESIZE
2015-08-18 11:52:52 -05:00
kelson
cf20ce5fae _js_cookies => not httponly_cookies 2015-08-18 12:25:13 -04:00
mdipierro
04c86f07ef Merge pull request #1032 from dsk7/allow_requires_login_to_be_determined_dynamically
Allow to specify a function for requires_login at auth decoration.
2015-08-18 11:03:37 -05:00
mdipierro
1a12c4011b Merge pull request #1031 from mmunz/master
Repair cache status page in appadmin for welcome, admin and examples …
2015-08-18 11:02:14 -05:00
mdipierro
85bbe15758 Merge pull request #1030 from josedesoto/issue/1028
fixed issue #1028, saml2 bug
2015-08-18 11:01:46 -05:00
mdipierro
5816481a44 Merge pull request #1029 from timnyborg/patch-3
simplejsonrpc: Fix TypeError when data is None
2015-08-18 11:01:19 -05:00
kelson
2675e9d229 added default HttpOnly cookies 2015-08-18 10:23:29 -04:00
mdipierro
41498917d5 Autocomplete(...at_beginning=False) 2015-08-16 15:22:45 -05:00
Cássio Botaro
8f7acd8154 Allow change quality when RESIZE 2015-08-13 02:12:49 -03:00
mdipierro
26865421b6 email change 2015-08-11 00:32:09 -05:00
mdipierro
b9ee4d4730 R-2.12.2 2015-08-09 09:27:42 -05:00
mdipierro
8fd7a27d5f fixed problem with pack all and missing cache folder 2015-08-09 09:26:57 -05:00
mdipierro
69231bdd7f R-1.12.1 2015-08-07 02:20:08 -05:00
mdipierro
55dfb9e8c4 R-1.12.1 2015-08-07 02:12:17 -05:00
mdipierro
7761219cba jquery 1.11.3, bootstrap 3.3.5 2015-08-07 02:10:27 -05:00
mdipierro
e31e4e236f prettydate can do UTC, fixes #1036 2015-08-07 02:04:07 -05:00
mdipierro
a43d822412 changed version for testing 2015-08-06 22:02:17 -05:00
dsk7
f94bc250eb Allow to specify a function for requires_login at auth decoration. 2015-08-02 13:21:20 +02:00
mdipierro
5775d2788d reverted apache processes=1 2015-08-01 00:24:21 -05:00
mdipierro
048f275076 fixed TLS support in ldap, thanks backseat 2015-08-01 00:21:56 -05:00
Manuel Munz
8078d4b0f3 Repair cache status page in appadmin for welcome, admin and examples apps.
This fixes how values are read. For cache.disk, we need to use the second value in the dict.
For cache.ram everything is already there in the object, no need trying to get the stats (which are not there) in the loop.
Also small fix for buttons that did not open the detailed statistics when clicked (class hidden has !important in bootstrap3).
2015-08-01 00:29:34 +02:00
Tim Nyborg
5ee8c9c930 simplejsonrpc: Fix TypeError when data is none 2015-07-27 12:07:26 +01:00
Jose de Soto
6659bc0793 fixed issue #1028, saml2 bug 2015-07-27 13:05:36 +02:00
mdipierro
d7caaf04cc fixed issue #933, wiki bug 2015-07-26 14:24:53 -05:00
mdipierro
e95115deb4 fixed order of confirm-password field 2015-07-26 10:18:45 -05:00
mdipierro
42c69b6343 changed indent 2015-07-20 02:26:13 -05:00
mdipierro
d2347dec41 CacheRepresenter 2015-07-20 02:17:59 -05:00
mdipierro
8420020c21 caching only refault represent for references 2015-07-20 02:07:47 -05:00
mdipierro
571fc6d919 changed version 2015-07-20 01:51:22 -05:00
mdipierro
52ec228eeb Merge branch 'master' of github.com:web2py/web2py 2015-07-20 01:20:22 -05:00
mdipierro
5848d9acaa Merge pull request #1023 from dmatic/master
Script to install web2py with nginx and uwsgi on centos 7
2015-07-19 11:35:31 -05:00
Dragan Matic
3e8cbd5a0d Script to install web2py with nginx and uwsgi on centos 7 2015-07-16 13:35:27 +02:00
mdipierro
e276cc2fc1 Merge pull request #1019 from gi0baro/master
Updated to pydal 15.07
2015-07-16 03:57:13 -05:00
mdipierro
39a048db61 Merge pull request #1018 from cassiobotaro/master
fix validations IS_IPV6 and IS_IPADDRESS
2015-07-16 03:57:06 -05:00
mdipierro
df4b896334 models speed up 2015-07-13 07:52:38 -05:00
gi0baro
6d58845153 Using pydal 15.07 2015-07-13 14:25:43 +02:00
Dragan Matic
ba1f8bf741 Merge pull request #1 from web2py/master
Update from original
2015-07-10 13:09:43 +02:00
cassiobotaro
a378ab3e51 fix validations IS_IPV6 and IS_IPADDRESS 2015-07-10 01:11:16 -03:00
mdipierro
2d866647e2 Merge pull request #1017 from raj454raj/master
Small typo
2015-07-07 03:55:17 -05:00
Raj
81863d69c9 Small typo
Sorry! Can't resist when you see typo on the terminal !
2015-07-07 02:38:30 +05:30
mdipierro
ee2879442f tracking last master since that works better at this time 2015-07-06 10:13:47 -05:00
mdipierro
ad68d2415d possibly fixed issue #243, SQLFORM.factory(..) and DAL(None) 2015-07-06 10:04:14 -05:00
mdipierro
928de67f8d fixed DAL(None) 2015-07-06 10:01:40 -05:00
mdipierro
68296f9e65 Merge pull request #1016 from ShySec/master
fix Field.Virtual use in multi-table queries
2015-07-06 07:44:49 -05:00
kelson
7ac6edae52 fix Field.Virtual use in multi-table queries 2015-07-06 08:35:58 -04:00
mdipierro
1fc90fdb6d scripts/web2py-scheduler.conf 2015-07-06 04:46:56 -05:00
mdipierro
34a9d72cde mail.settings.server='logging:filename' 2015-07-06 04:45:14 -05:00
mdipierro
198ce939d0 fixed css of population table 2015-07-04 17:12:25 -05:00
mdipierro
e31a099cb3 Merge pull request #1012 from ortgit/master
Security fix: Validate for open redirect everywhere, not just in login()
2015-07-02 06:40:12 -05:00
mdipierro
cc7e10d216 Merge pull request #1009 from rserbitar/master
Fix compatibility with Tornado 4
2015-07-02 06:38:35 -05:00
mdipierro
d8b68036c2 Merge pull request #1006 from kjkuan/workaround-urllib-bug
Workaround http://bugs.python.org/issue9405 on OS X.
2015-07-02 06:38:18 -05:00
pallav_fdsi
f9cd7e4ef4 Open redirect attacks should be caught for all functions that use the _next variable (for example: logout()) instead of just for the login() function. 2015-07-01 18:38:43 -04:00
pallav_fdsi
896b45b838 Merge branch 'master' of https://github.com/web2py/web2py 2015-07-01 17:49:47 -04:00
mdipierro
d6146c9c5d no more text shadow in buttons, was horrible 2015-07-01 12:18:10 -05:00
mdipierro
b3be806244 Merge pull request #1008 from leonelcamara/pack_exe
Moved pack as exe functionality to the pack_custom.html form
2015-07-01 09:01:49 -05:00
rserbitar
eac12d3a57 Fix compatibility with Tornado 4
http://stackoverflow.com/questions/24851207/tornado-403-get-warning-when-opening-websocket
2015-07-01 12:06:52 +02:00
mdipierro
2fc081bc3c no more module_installer 2015-06-30 16:12:54 -05:00
Leonel Câmara
032af7c04d Moved pack as exe functionality to the pack_custom.html form 2015-06-30 19:19:38 +01:00
mdipierro
8e63825def Merge pull request #1007 from leonelcamara/pack_exe
Allow packing an application with the windows executable
2015-06-30 12:32:05 -05:00
Leonel Câmara
5d2e5dded3 Allow packing an application with the windows executable 2015-06-30 17:51:39 +01:00
mdipierro
61e33da844 module installer 2015-06-30 11:14:47 -05:00
Jack Kuan
da9dbaa5d6 Workaround http://bugs.python.org/issue9405 on OS X.
See https://groups.google.com/forum/#!topic/web2py/WSjhhDet1UM
for context.
2015-06-30 10:56:31 -04:00
mdipierro
7543c54bdb enable_tokens in welcome 2015-06-30 02:32:44 -05:00
mdipierro
00608e4f04 auth.settings.enable_tokens and header web2py_user_token 2015-06-29 13:38:54 -05:00
mdipierro
cdbf48f09b fixed margin-top in welcome 2015-06-29 13:03:23 -05:00
mdipierro
f39db6331a dealing with issue of accidentally redefining request/response, thanks Auden RovelleQuartz 2015-06-29 03:56:22 -05:00
mdipierro
ef433da190 improvements to token logic, thanks Niphlod 2015-06-28 17:01:21 -05:00
mdipierro
d2375b4187 always allow access to <app>/appadmin/manage/auth if used is admin 2015-06-28 16:41:09 -05:00
mdipierro
26d87967c5 always allow access to appadmin/manage.auth if used is admin 2015-06-28 16:40:23 -05:00
mdipierro
044b2331c3 bulk_register_enabled=False 2015-06-28 10:30:05 -05:00
mdipierro
c89614ada6 more strict conditions on bulk_register 2015-06-28 10:20:33 -05:00
mdipierro
f0aba167b4 _token, not token 2015-06-28 09:51:45 -05:00
mdipierro
bde9562b78 api_tokens in example 2015-06-28 09:49:50 -05:00
mdipierro
9a1229470a support for api_tokens 2015-06-28 09:48:08 -05:00
mdipierro
f781b9e1f5 Merge branch 'peregrinius-master' 2015-06-28 09:08:54 -05:00
mdipierro
fa32b7577b fixed a bug and added support for user/bulk_register 2015-06-28 09:01:10 -05:00
mdipierro
68526a0c6d allow unsorted multiword query in grid search 2015-06-28 07:52:34 -05:00
mdipierro
ad2003c618 fixed recently introduced sanitize bug 2015-06-27 00:33:16 -05:00
mdipierro
c1ecf823d8 added link to new tutorial 2015-06-26 08:07:31 -05:00
mdipierro
6134f82452 fixed issuer #239, locking error on FreeBSD, thanks josejachuf 2015-06-26 06:58:07 -05:00
mdipierro
fbb5a8b9bb fixed issue #968, IS_MATCH validator bug with unicode, thanks alex0834 2015-06-25 04:36:16 -05:00
mdipierro
df34869d65 fixes #978, autotypes and unicode strings, thanks remcoboerma 2015-06-25 04:31:41 -05:00
mdipierro
28e6999e7d fixed issue #980, Admin application: can't access directories with space in directory name, thanks mmihaltz 2015-06-25 04:30:10 -05:00
mdipierro
f4f77b0cb6 fixed issue #982, LOAD with ajax=False and args 2015-06-25 04:23:49 -05:00
mdipierro
23ddb6c3c2 fixed issue #999, gluon.sanitiizer.sanitze improvement, thanks macfiron 2015-06-25 04:19:01 -05:00
mdipierro
b636a5d6e9 more companies 2015-06-25 03:26:18 -05:00
mdipierro
efc392966e Merge pull request #1005 from cs09g/patch-3
change misspelling
2015-06-18 06:31:51 -05:00
mdipierro
cffa59a80c Merge pull request #1004 from cs09g/patch-2
simplified conditions
2015-06-18 06:31:22 -05:00
mdipierro
82a1b9f628 Merge pull request #1003 from cs09g/patch-1
remove duplicated execution
2015-06-18 06:31:08 -05:00
mdipierro
94d2f1453d Merge pull request #1002 from cs09g/patch-5
change initialization
2015-06-18 06:30:30 -05:00
mdipierro
a1875ee362 Merge pull request #1001 from cs09g/patch-3
Update appadmin.py
2015-06-18 06:29:57 -05:00
mdipierro
5f13dca712 Merge pull request #1000 from niphlod/fix/readme
after appveyor hooks have been defined...
2015-06-18 06:28:39 -05:00
Chase choi
f78d423c92 change misspelling 2015-06-18 17:13:38 +09:00
Chase choi
f60ae809b6 simplified conditions 2015-06-18 17:05:34 +09:00
Chase choi
34dd8af101 remove duplicated execution
is that really duplicated or missing something to add for difference?
2015-06-18 16:55:04 +09:00
Chase choi
6bf6ebab1b change initialization
ok must be True or False. 
set False is better for initialization
2015-06-18 16:40:59 +09:00
Chase choi
29bf50425b Update appadmin.py 2015-06-18 16:31:28 +09:00
niphlod
8a7612c976 after appveyor hooks have been defined... 2015-06-17 21:23:29 +02:00
mdipierro
97489fd277 Merge pull request #998 from niphlod/build/appveyor
move to codecov and enable appveyor too
2015-06-17 02:00:48 -05:00
mdipierro
b86184fe58 Merge pull request #997 from niphlod/fix/bs3_grid_checkbox
fix display of checkboxes in search form of grid
2015-06-17 02:00:34 -05:00
niphlod
2ce53e9957 move to codecov and enable appveyor too 2015-06-14 21:17:52 +02:00
niphlod
d61c372c95 fix display of checkboxes in search form of grid 2015-06-11 21:54:21 +02:00
mdipierro
73e176365f Merge pull request #995 from niphlod/fix/994
fixes #994
2015-06-07 21:47:28 -05:00
mdipierro
33f12d91a5 Merge pull request #992 from btreecat/master
Fixed authentication using different login methods.
2015-06-07 21:47:08 -05:00
mdipierro
d0f1286f03 Merge pull request #991 from cs09g/patch-2
Update appadmin.py
2015-06-07 21:44:44 -05:00
mdipierro
04d698109e Merge pull request #990 from cs09g/patch-1
remove invalid initialize
2015-06-07 21:44:19 -05:00
mdipierro
0e9c5caf4d added request_reset_password_on... 2015-06-07 21:28:18 -05:00
niphlod
509b0a6987 fixes is_in_set repr too 2015-06-06 09:50:44 +02:00
niphlod
e0074ebcac fixes #994
we were overriding default classes for specific widgets
2015-06-05 22:10:33 +02:00
Stephen Tanner
918fdf2f0c Fixed authentication using different login methods. 2015-06-02 12:24:35 -04:00
Chase choi
8e827f7a09 Update appadmin.py 2015-06-02 01:28:51 +09:00
Chase choi
cf2d5b637b remove invalid initialize 2015-06-02 01:00:25 +09:00
peregrinius
a2e7794b92 Invite user
Invite by email another user to access your application. Note, my
initial version was built on Auth.register_bare which doesn't seem to be
in this repository???
2015-05-29 15:22:36 +12:00
pallav_fdsi
842207ab33 Merge branch 'master' of https://github.com/web2py/web2py 2015-04-04 19:51:26 -04:00
pallav_fdsi
c58f29bb9c Merge branch 'master' of https://github.com/web2py/web2py 2015-01-25 15:58:39 -05:00
pallav_fdsi
0b0f82b514 Merge branch 'master' of https://github.com/web2py/web2py 2014-11-21 00:29:14 -05:00
pallav_fdsi
3ab8a7bfd6 Merge branch 'master' of https://github.com/web2py/web2py 2014-11-11 00:21:27 -05:00
pallav_fdsi
c5a9d2c456 Ignore the contents of the site-packages directory 2014-11-06 10:35:49 -05:00
79 changed files with 3126 additions and 1246 deletions

2
.gitignore vendored
View File

@@ -58,3 +58,5 @@ HOWTO-web2py-devel
*.sublime-project
*.sublime-workspace
.idea/*
site-packages/
logs/

View File

@@ -17,14 +17,21 @@ install:
before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache python-coveralls; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi
- mysql -e 'create database pydal;'
- psql -c 'create database pydal;' -U postgres
- psql -c 'create extension postgis;' -U postgres -d pydal;
- psql -c 'SHOW SERVER_VERSION' -U postgres
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then codecov; fi
notifications:
email: true
addons:
postgresql: "9.4"

View File

@@ -1,3 +1,36 @@
## 2.13.1-2
- fixed a security issue in request_reset_password
- added fabfile.py
- fixed oauth2 renew token, thanks dokime7
- fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning)
- allow passing unicode to template render
- allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg
- allow HttpOnly cookies
- french pluralizaiton rules, thanks Mathieu Clabaut
- fixed bug in redirect to cas service, thanks Fernando González
- allow deploying to pythonanywhere from the web2py admin that you're running locally, thanks Leonel
- better tests
- many more bug fixes
## 2.12.1-3
- security fix: Validate for open redirect everywhere, not just in login()
- allow to pack invidual apps and selected files as packed exe files
- allow bulk user registration with default bulk_register_enabled=False
- allow unsorted multiword query in grid search
- better MongoDB support with newer pyDAL
- enable <app>/appadmin/manage/auth by default for user admin
- allow mail.settings.server='logging:filename' to log emails to a file
- better caching logic
- fixed order of confirm-password field
- TLS support in ldap
- prettydate can do UTC
- jquery 1.11.3
- bootstrap 3.3.5
- moved to codecov and enabled appveyor
- many bug fixes
## 2.11.1
- Many small but significative improvements and bug fixes

View File

@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.11.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.13.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps

View File

@@ -38,9 +38,10 @@ PyDAL uses a separate stable release cycle to the rest of web2py. PyDAL releases
## Tests
[![Build Status](https://img.shields.io/travis/web2py/web2py.svg?style=flat-square)](https://travis-ci.org/web2py/web2py)
[![Build Status](https://img.shields.io/travis/web2py/web2py/master.svg?style=flat-square&label=Travis-CI)](https://travis-ci.org/web2py/web2py)
[![MS Build Status](https://img.shields.io/appveyor/ci/web2py/web2py/master.svg?style=flat-square&label=Appveyor-CI)](https://ci.appveyor.com/project/web2py/web2py)
[![Coverage Status](https://img.shields.io/codecov/c/github/web2py/web2py.svg?style=flat-square)](https://codecov.io/github/web2py/web2py)
[![Coverage Status](https://img.shields.io/coveralls/web2py/web2py.svg?style=flat-square)](https://coveralls.io/r/web2py/web2py)
## Installation Instructions
@@ -63,7 +64,7 @@ That's it!!!
packages/ > web2py submodules
dal/
contrib/ > third party libraries
tests/ > unittests
tests/ > unittests
applications/ > are the apps
admin/ > web based IDE
...

View File

@@ -1 +1 @@
Version 2.11.2-stable+timestamp.2015.05.30.11.29.46
Version 2.13.3-stable+timestamp.2015.12.24.08.08.22

View File

@@ -49,7 +49,8 @@ if request.function == 'manage':
auth.table_group(),
auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None
auth.requires_membership(manager_role)(lambda: None)()
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized")
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -80,7 +81,6 @@ if False and request.tickets_db:
def get_databases(request):
dbs = {}
for (key, value) in global_env.items():
cond = False
try:
cond = isinstance(value, GQLDB)
except:
@@ -420,7 +420,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -445,30 +445,31 @@ def ccache():
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
total.update(gae_stats)
else:
# get ram stats directly from the cache object
ram_stats = cache.ram.stats[request.application]
ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
ram['misses'] = ram_stats['misses']
try:
ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
except (KeyError, ZeroDivisionError):
ram['ratio'] = 0
for key, value in cache.ram.storage.iteritems():
if isinstance(value, dict):
ram['hits'] = value['hit_total'] - value['misses']
ram['misses'] = value['misses']
try:
ram['ratio'] = ram['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
ram['ratio'] = 0
else:
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
ram['entries'] += 1
if value[0] < ram['oldest']:
ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
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 isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
if isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
@@ -480,12 +481,12 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
total['objects'] = ram['objects'] + disk['objects']
total['hits'] = ram['hits'] + disk['hits']
total['misses'] = ram['misses'] + disk['misses']
total['keys'] = ram['keys'] + disk['keys']
ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
total[key] = ram[key] + disk[key]
try:
total['ratio'] = total['hits'] * 100 / (total['hits'] +
total['misses'])
@@ -575,11 +576,9 @@ def bg_graph_model():
meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))

View File

@@ -220,7 +220,7 @@ def list_breakpoints():
"Return a list of linenumbers for current breakpoints"
breakpoints = []
ok = None
ok = False
try:
filename = os.path.join(request.env['applications_parent'],
'applications', request.vars.filename)
@@ -235,5 +235,4 @@ def list_breakpoints():
ok = True
except Exception, e:
session.flash = str(e)
ok = False
return response.json({'ok': ok, 'breakpoints': breakpoints})

View File

@@ -292,9 +292,6 @@ def site():
log_progress(appname)
session.flash = T(msg, dict(appname=appname,
digest=md5_hash(installed)))
elif f and form_update.vars.overwrite:
msg = 'unable to install application "%(appname)s"'
session.flash = T(msg, dict(appname=form_update.vars.name))
else:
msg = 'unable to install application "%(appname)s"'
session.flash = T(msg, dict(appname=form_update.vars.name))
@@ -370,25 +367,56 @@ def pack_plugin():
session.flash = T('internal error')
redirect(URL('plugin', args=request.args))
def pack_exe(app, base, filenames=None):
import urllib
import zipfile
from cStringIO import StringIO
# Download latest web2py_win and open it with zipfile
download_url = 'http://www.web2py.com/examples/static/web2py_win.zip'
out = StringIO()
out.write(urllib.urlopen(download_url).read())
web2py_win = zipfile.ZipFile(out, mode='a')
# Write routes.py with the application as default
routes = u'# -*- coding: utf-8 -*-\nrouters = dict(BASE=dict(default_application="%s"))' % app
web2py_win.writestr('web2py/routes.py', routes.encode('utf-8'))
# Copy the application into the zipfile
common_root = os.path.dirname(base)
for filename in filenames:
fname = os.path.join(base, filename)
arcname = os.path.join('web2py/applications', app, filename)
web2py_win.write(fname, arcname)
web2py_win.close()
response.headers['Content-Type'] = 'application/zip'
response.headers['Content-Disposition'] = 'attachment; filename=web2py.app.%s.zip' % app
out.seek(0)
return response.stream(out)
def pack_custom():
app = get_app()
base = apath(app, r=request)
if request.post_vars.file:
files = request.post_vars.file
files = [files] if not isinstance(files,list) else files
fname = 'web2py.app.%s.w2p' % app
try:
filename = app_pack(app, request, raise_ex=True, filenames=files)
except Exception, e:
filename = None
if filename:
response.headers['Content-Type'] = 'application/w2p'
disposition = 'attachment; filename=%s' % fname
response.headers['Content-Disposition'] = disposition
return safe_read(filename, 'rb')
if request.post_vars.doexe is None:
fname = 'web2py.app.%s.w2p' % app
try:
filename = app_pack(app, request, raise_ex=True, filenames=files)
except Exception, e:
filename = None
if filename:
response.headers['Content-Type'] = 'application/w2p'
disposition = 'attachment; filename=%s' % fname
response.headers['Content-Disposition'] = disposition
return safe_read(filename, 'rb')
else:
session.flash = T('internal error: %s', e)
redirect(URL(args=request.args))
else:
session.flash = T('internal error: %s', e)
redirect(URL(args=request.args))
return pack_exe(app, base, files)
def ignore(fs):
return [f for f in fs if not (
f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))]
@@ -456,9 +484,15 @@ def cleanup():
def compile_app():
app = get_app()
c = app_compile(app, request)
c = app_compile(app, request,
skip_failed_views = (request.args(1) == 'skip_failed_views'))
if not c:
session.flash = T('application compiled')
elif isinstance(c, list):
session.flash = DIV(*[T('application compiled'), BR(), BR(),
T('WARNING: The following views could not be compiled:'), BR()] +
[CAT(BR(), view) for view in c] +
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
else:
session.flash = DIV(T('Cannot compile: there are errors in your app:'),
CODE(c))
@@ -867,13 +901,9 @@ def resolve():
def getclass(item):
""" Determine item class """
if item[0] == ' ':
return 'normal'
if item[0] == '+':
return 'plus'
if item[0] == '-':
return 'minus'
operators = {' ':'normal', '+':'plus', '-':'minus'}
return operators[item[0]]
if request.vars:
c = '\n'.join([item[2:].rstrip() for (i, item) in enumerate(d) if item[0]
@@ -1510,7 +1540,7 @@ def upload_file():
if filename:
d = dict(filename=filename[len(path):])
else:
d = dict(filename='unkown')
d = dict(filename='unknown')
session.flash = T('cannot upload file "%(filename)s"', d)
redirect(request.vars.sender)

View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
import base64
import os
import re
import gzip
import tarfile
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from xmlrpclib import ProtocolError
from gluon.contrib.simplejsonrpc import ServerProxy
def deploy():
response.title = T('Deploy to pythonanywhere')
return {}
def create_account():
""" Create a PythonAnywhere account """
if not request.vars:
raise HTTP(400)
if request.vars.username and request.vars.web2py_admin_password:
# Check if web2py is already there otherwise we get an error 500 too.
client = ServerProxy('https://%(username)s:%(web2py_admin_password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
try:
if client.login() is True:
return response.json({'status': 'ok'})
except ProtocolError as error:
pass
import urllib, urllib2
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
data = urllib.urlencode(request.vars)
req = urllib2.Request(url, data)
try:
reply = urllib2.urlopen(req)
except urllib2.HTTPError as error:
if error.code == 400:
reply = error
elif error.code == 500:
return response.json({'status':'error', 'errors':{'username': ['An App other than web2py is installed in the domain %(username)s.pythonanywhere.com' % request.vars]}})
else:
raise
response.headers['Content-Type'] = 'application/json'
return reply.read()
def list_apps():
""" Get a list of apps both remote and local """
if not request.vars.username or not request.vars.password:
raise HTTP(400)
client = ServerProxy('https://%(username)s:%(password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
regex = re.compile('^\w+$')
local = [f for f in os.listdir(apath(r=request)) if regex.match(f)]
try:
pythonanywhere = client.list_apps()
except ProtocolError as error:
raise HTTP(error.errcode)
return response.json({'local': local, 'pythonanywhere': pythonanywhere})
def bulk_install():
""" Install a list of apps """
def b64pack(app):
"""
Given an app's name, return the base64 representation of its packed version.
"""
folder = apath(app, r=request)
tmpfile = StringIO()
tar = tarfile.TarFile(fileobj=tmpfile, mode='w')
try:
filenames = listdir(folder, '^[\w\.\-]+$', add_dirs=True,
exclude_content_from=['cache', 'sessions', 'errors'])
for fname in filenames:
tar.add(os.path.join(folder, fname), fname, False)
finally:
tar.close()
tmpfile.seek(0)
gzfile = StringIO()
w2pfp = gzip.GzipFile(fileobj=gzfile, mode='wb')
w2pfp.write(tmpfile.read())
w2pfp.close()
gzfile.seek(0)
return base64.b64encode(gzfile.read())
request.vars.apps = request.vars['apps[]']
if not request.vars.apps or not request.vars.username or not request.vars.password:
raise HTTP(400)
if not isinstance(request.vars.apps, list):
request.vars.apps = [request.vars.apps] # Only one app selected
client = ServerProxy('https://%(username)s:%(password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
for app in request.vars.apps:
try:
client.install(app, app+'.w2p', b64pack(app))
except ProtocolError as error:
raise HTTP(error.errcode)
return response.json({'status': 'ok'})

View File

@@ -1,377 +1,408 @@
# -*- coding: utf-8 -*-
{
'!langcode!': 'pt',
'!langname!': 'Português',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novo_valor\'". Não é permitido atualizar ou apagar resultados de um JOIN',
'%s %%{row} deleted': '%s registros apagados',
'%s %%{row} updated': '%s registros atualizados',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'(requires internet access)': '(requer acesso à internet)',
'(requires internet access, experimental)': '(requer acesso à internet, experimental)',
'(something like "it-it")': '(algo como "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
'A new version of web2py is available: %s': 'Está disponível uma nova versão do web2py: %s',
'About': 'sobre',
'About application': 'Sobre a aplicação',
'additional code for your application': 'código adicional para sua aplicação',
'Additional code for your application': 'Código adicional para a sua aplicação',
'admin disabled because no admin password': ' admin desabilitado por falta de senha definida',
'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE',
'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha',
'Admin is disabled because insecure channel': 'Admin desabilitado pois o canal não é seguro',
'Admin is disabled because unsecure channel': 'Admin desabilitado pois o canal não é seguro',
'Admin language': 'Linguagem do Admin',
'administrative interface': 'interface administrativa',
'Administrator Password:': 'Senha de administrador:',
'and rename it (required):': 'e renomeie (requerido):',
'and rename it:': ' e renomeie:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'admin desabilitado, canal inseguro',
'application "%s" uninstalled': 'aplicação "%s" desinstalada',
'application compiled': 'aplicação compilada',
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
'Application name:': 'Nome da aplicação:',
'are not used': 'não usadas',
'are not used yet': 'ainda não usadas',
'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'Are you sure you want to uninstall application "%s"': 'Tem certeza que deseja apagar a aplicação "%s"?',
'Are you sure you want to uninstall application "%s"?': 'Tem certeza que deseja apagar a aplicação "%s"?',
'Are you sure you want to upgrade web2py now?': 'Tem certeza que deseja atualizar o web2py agora?',
'arguments': 'argumentos',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.',
'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!',
'Autocomplete Python Code': 'Autocompletar Código Python',
'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
'back': 'voltar',
'browse': 'buscar',
'cache': 'cache',
'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas',
'can be a git repo': 'can be a git repo',
'Cannot be empty': 'Não pode ser vazio',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente',
'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação',
'cannot create file': 'Não é possível criar o arquivo',
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
'Change admin password': 'mudar senha de administrador',
'change editor settings': 'mudar definições do editor',
'Change Password': 'Trocar Senha',
'check all': 'marcar todos',
'Check for upgrades': 'checar por atualizações',
'Check to delete': 'Marque para apagar',
'Checking for upgrades...': 'Buscando atualizações...',
'Clean': 'limpar',
'click here for online examples': 'clique para ver exemplos online',
'click here for the administrative interface': 'Clique aqui para acessar a interface administrativa',
'Click row to expand traceback': 'Clique em uma coluna para expandir o log do erro',
'click to check for upgrades': 'clique aqui para checar por atualizações',
'click to open': 'clique para abrir',
'Client IP': 'IP do cliente',
'code': 'código',
'collapse/expand all': 'colapsar/expandir tudo',
'commit (mercurial)': 'commit (mercurial)',
'Compile': 'compilar',
'compiled application removed': 'aplicação compilada removida',
'Controllers': 'Controladores',
'controllers': 'controladores',
'Count': 'Contagem',
'Create': 'criar',
'create file with filename:': 'criar um arquivo com o nome:',
'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente',
'create new application:': 'nome da nova aplicação:',
'Create new simple application': 'Crie uma nova aplicação',
'Create/Upload': 'Create/Upload',
'created by': 'criado por',
'crontab': 'crontab',
'Current request': 'Requisição atual',
'Current response': 'Resposta atual',
'Current session': 'Sessão atual',
'currently running': 'Executando',
'currently saved or': 'Atualmente salvo ou',
'customize me!': 'Modifique-me',
'data uploaded': 'Dados enviados',
'database': 'banco de dados',
'database %s select': 'Seleção no banco de dados %s',
'database administration': 'administração de banco de dados',
'Date and Time': 'Data e Hora',
'db': 'db',
'Debug': 'Debug',
'defines tables': 'define as tabelas',
'Delete': 'Apague',
'delete': 'apagar',
'delete all checked': 'apagar marcados',
'delete plugin': 'apagar plugin',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Delete:': 'Apague:',
'Deploy': 'publicar',
'Deploy on Google App Engine': 'Publicar no Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Description': 'Descrição',
'design': 'modificar',
'DESIGN': 'Projeto',
'Design for': 'Projeto de',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direção: ltr',
'Disable': 'Disable',
'docs': 'docs',
'done!': 'feito!',
'download layouts': 'download layouts',
'Download layouts from repository': 'Download layouts from repository',
'download plugins': 'download plugins',
'Download plugins from repository': 'Download plugins from repository',
'E-mail': 'E-mail',
'EDIT': 'EDITAR',
'Edit': 'editar',
'Edit application': 'Editar aplicação',
'edit controller': 'editar controlador',
'Edit current record': 'Editar o registro atual',
'Edit Profile': 'Editar Perfil',
'edit views:': 'editar visões:',
'Editing %s': 'A Editar %s',
'Editing file': 'Editando arquivo',
'Editing file "%s"': 'Editando arquivo "%s"',
'Editing Language file': 'Editando arquivo de linguagem',
'Enterprise Web Framework': 'Framework web empresarial',
'Error': 'Erro',
'Error logs for "%(app)s"': 'Logs de erro para "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'erros',
'Exception instance attributes': 'Atributos da instancia de excessão',
'Exit Fullscreen': 'Sair de Ecrã Inteiro',
'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)',
'export as csv file': 'exportar como arquivo CSV',
'exposes': 'expõe',
'extends': 'estende',
'failed to reload module': 'Falha ao recarregar o módulo',
'failed to reload module because:': 'falha ao recarregar o módulo por:',
'File': 'Arquivo',
'file "%(filename)s" created': 'arquivo "%(filename)s" criado',
'file "%(filename)s" deleted': 'arquivo "%(filename)s" apagado',
'file "%(filename)s" uploaded': 'arquivo "%(filename)s" enviado',
'file "%(filename)s" was not deleted': 'arquivo "%(filename)s" não foi apagado',
'file "%s" of %s restored': 'arquivo "%s" de %s restaurado',
'file changed on disk': 'arquivo modificado no disco',
'file does not exist': 'arquivo não existe',
'file saved on %(time)s': 'arquivo salvo em %(time)s',
'file saved on %s': 'arquivo salvo em %s',
'filter': 'filtro',
'Find Next': 'Localizar Seguinte',
'Find Previous': 'Localizar Anterior',
'First name': 'Nome',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].',
'graph model': 'graph model',
'Group ID': 'ID do Grupo',
'Hello World': 'Olá Mundo',
'Help': 'ajuda',
'Hide/Show Translated strings': '',
'htmledit': 'htmledit',
'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.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.',
'Import/Export': 'Importar/Exportar',
'includes': 'inclui',
'insert new': 'inserir novo',
'insert new %s': 'inserir novo %s',
'inspect attributes': 'inspecionar atributos',
'Install': 'instalar',
'Installed applications': 'Aplicações instaladas',
'internal error': 'erro interno',
'Internal State': 'Estado Interno',
'Invalid action': 'Ação inválida',
'Invalid email': 'E-mail inválido',
'invalid password': 'senha inválida',
'Invalid Query': 'Consulta inválida',
'invalid request': 'solicitação inválida',
'invalid ticket': 'ticket inválido',
'Keyboard shortcuts': 'Atalhos de teclado',
'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado',
'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados',
'languages': 'linguagens',
'Languages': 'Linguagens',
'languages updated': 'linguagens atualizadas',
'Last name': 'Sobrenome',
'Last saved on:': 'Salvo em:',
'License for': 'Licença para',
'loading...': 'carregando...',
'locals': 'locals',
'Login': 'Entrar',
'login': 'inicio de sessão',
'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
'Logout': 'finalizar sessão',
'Lost Password': 'Senha perdida',
'Manage': 'Manage',
'manage': 'gerenciar',
'merge': 'juntar',
'Models': 'Modelos',
'models': 'modelos',
'Modules': 'Módulos',
'modules': 'módulos',
'Name': 'Nome',
'new application "%s" created': 'nova aplicação "%s" criada',
'New application wizard': 'Assistente para novas aplicações ',
'new plugin installed': 'novo plugin instalado',
'New Record': 'Novo registro',
'new record inserted': 'novo registro inserido',
'New simple application': 'Nova aplicação básica',
'next 100 rows': 'próximos 100 registros',
'NO': 'NÃO',
'No databases in this application': 'Não existem bancos de dados nesta aplicação',
'no match': 'não encontrado',
'no package selected': 'nenhum pacote selecionado',
'online designer': 'online designer',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Ou Obtenha do URL:',
'or import from csv file': 'ou importar de um arquivo CSV',
'or provide app url:': 'ou forneça a url de uma aplicação:',
'or provide application url:': 'ou forneça a url de uma aplicação:',
'Origin': 'Origem',
'Original/Translation': 'Original/Tradução',
'Overwrite installed app': 'sobrescrever aplicação instalada',
'Pack all': 'criar pacote',
'Pack compiled': 'criar pacote compilado',
'Pack custom': 'Pack custom',
'pack plugin': 'empacotar plugin',
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
'Password': 'Senha',
'password changed': 'senha alterada',
'Peeking at file': 'Visualizando arquivo',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" eliminado',
'Plugin "%s" in application': 'Plugin "%s" na aplicação',
'plugins': 'plugins',
'Plugins': 'Plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Este site utiliza',
'previous 100 rows': '100 registros anteriores',
'Private files': 'Private files',
'private files': 'private files',
'Query:': 'Consulta:',
'Rapid Search': 'Rapid Search',
'record': 'registro',
'record does not exist': 'o registro não existe',
'record id': 'id do registro',
'Record ID': 'ID do Registro',
'Register': 'Registrar-se',
'Registration key': 'Chave de registro',
'Reload routes': 'Reload routes',
'Remove compiled': 'eliminar compilados',
'Replace': 'Substituir',
'Replace All': 'Substituir Tudo',
'request': 'request',
'Resolve Conflict file': 'Arquivo de resolução de conflito',
'response': 'response',
'restore': 'restaurar',
'revert': 'reverter',
'Role': 'Papel',
'Rows in table': 'Registros na tabela',
'Rows selected': 'Registros selecionados',
'rules are not defined': 'rules are not defined',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'A correr em %s',
'Save': 'Save',
'save': 'salvar',
'Save file:': 'Gravar ficheiro:',
'Save file: %s': 'Gravar ficheiro: %s',
'Save via Ajax': 'Gravar via Ajax',
'Saved file hash:': 'Hash do arquivo salvo:',
'selected': 'selecionado(s)',
'session': 'session',
'session expired': 'sessão expirada',
'shell': 'Terminal',
'Site': 'site',
'some files could not be removed': 'alguns arquicos não puderam ser removidos',
'Start searching': 'Start searching',
'Start wizard': 'iniciar assistente',
'state': 'estado',
'Static': 'Static',
'static': 'estáticos',
'Static files': 'Arquivos estáticos',
'Submit': 'Submit',
'submit': 'enviar',
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
'table': 'tabela',
'Table name': 'Nome da tabela',
'test': 'testar',
'Testing application': 'Testando a aplicação',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.',
'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates',
'There are no controllers': 'Não existem controllers',
'There are no models': 'Não existem modelos',
'There are no modules': 'Não existem módulos',
'There are no plugins': 'There are no plugins',
'There are no private files': '',
'There are no static files': 'Não existem arquicos estáticos',
'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada',
'There are no views': 'Não existem visões',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
'This is the %(filename)s template': 'Este é o template %(filename)s',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'Timestamp': 'Data Atual',
'TM': 'MR',
'to previous version.': 'para a versão anterior.',
'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle comment': 'Toggle comment',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'translation strings for the application': 'textos traduzidos para a aplicação',
'Translation strings for the application': 'Translation strings for the application',
'try': 'tente',
'try something like': 'tente algo como',
'Try the mobile interface': 'Try the mobile interface',
'Unable to check for upgrades': 'Não é possível checar as atualizações',
'unable to create application "%s"': 'não é possível criar a aplicação "%s"',
'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"',
'unable to delete file plugin "%(plugin)s"': 'não é possível criar o plugin "%(plugin)s"',
'Unable to download': 'Não é possível efetuar o download',
'Unable to download app': 'Não é possível baixar a aplicação',
'Unable to download app because:': 'Não é possível baixar a aplicação porque:',
'Unable to download because': 'Não é possível baixar porque',
'unable to parse csv file': 'não é possível analisar o arquivo CSV',
'unable to uninstall "%s"': 'não é possível instalar "%s"',
'unable to upgrade because "%s"': 'não é possível atualizar porque "%s"',
'uncheck all': 'desmarcar todos',
'Uninstall': 'desinstalar',
'update': 'atualizar',
'update all languages': 'atualizar todas as linguagens',
'Update:': 'Atualizar:',
'upgrade web2py now': 'atualize o web2py agora',
'upload': 'upload',
'Upload': 'Upload',
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
'Upload a package:': 'Faça upload de um pacote:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'Fazer upload de uma aplicação:',
'Upload existing application': 'Faça upload de uma aplicação existente',
'upload file:': 'Enviar arquivo:',
'upload plugin file:': 'Enviar arquivo de plugin:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para criar consultas mais complexas.',
'Use an url:': 'Use uma url:',
'User ID': 'ID do Usuario',
'variables': 'variáveis',
'Version': 'Versão',
'versioning': 'versionamento',
'Versioning': 'Versioning',
'view': 'visão',
'Views': 'Visões',
'views': 'visões',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py está atualizado',
'web2py Recent Tweets': 'Tweets Recentes de @web2py',
'web2py upgraded; please restart it': 'web2py atualizado; favor reiniciar',
'Welcome to web2py': 'Bem-vindo ao web2py',
'YES': 'SIM',
}
# -*- coding: utf-8 -*-
{
'!langcode!': 'pt',
'!langname!': 'Português',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novo_valor\'". Não é permitido atualizar ou apagar resultados de um JOIN',
'%s %%{row} deleted': '%s registros apagados',
'%s %%{row} updated': '%s registros atualizados',
'%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'(requires internet access)': '(requer acesso à internet)',
'(requires internet access, experimental)': '(requer acesso à internet, experimental)',
'(something like "it-it")': '(algo como "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
'A new version of web2py is available: %s': 'Está disponível uma nova versão do web2py: %s',
'About': 'sobre',
'About application': 'Sobre a aplicação',
'Accept Terms': 'Accept Terms',
'additional code for your application': 'código adicional para sua aplicação',
'Additional code for your application': 'Código adicional para a sua aplicação',
'admin disabled because no admin password': ' admin desabilitado por falta de senha definida',
'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE',
'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha',
'Admin is disabled because insecure channel': 'Admin desabilitado pois o canal não é seguro',
'Admin is disabled because unsecure channel': 'Admin desabilitado pois o canal não é seguro',
'Admin language': 'Linguagem do Admin',
'administrative interface': 'interface administrativa',
'Administrator Password:': 'Senha de administrador:',
'and rename it (required):': 'e renomeie (requerido):',
'and rename it:': ' e renomeie:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'admin desabilitado, canal inseguro',
'application "%s" uninstalled': 'aplicação "%s" desinstalada',
'application compiled': 'aplicação compilada',
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
'Application name:': 'Nome da aplicação:',
'are not used': 'não usadas',
'are not used yet': 'ainda não usadas',
'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'Are you sure you want to uninstall application "%s"': 'Tem certeza que deseja apagar a aplicação "%s"?',
'Are you sure you want to uninstall application "%s"?': 'Tem certeza que deseja apagar a aplicação "%s"?',
'Are you sure you want to upgrade web2py now?': 'Tem certeza que deseja atualizar o web2py agora?',
'arguments': 'argumentos',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.',
'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!',
'Autocomplete Python Code': 'Autocompletar Código Python',
'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
'back': 'voltar',
'Begin': 'Begin',
'browse': 'buscar',
'cache': 'cache',
'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas',
'can be a git repo': 'can be a git repo',
'Cannot be empty': 'Não pode ser vazio',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente',
'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação',
'cannot create file': 'Não é possível criar o arquivo',
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
'Change admin password': 'mudar senha de administrador',
'change editor settings': 'mudar definições do editor',
'Change Password': 'Trocar Senha',
'check all': 'marcar todos',
'Check for upgrades': 'checar por atualizações',
'Check to delete': 'Marque para apagar',
'Checking for upgrades...': 'Buscando atualizações...',
'Clean': 'limpar',
'click here for online examples': 'clique para ver exemplos online',
'click here for the administrative interface': 'Clique aqui para acessar a interface administrativa',
'Click row to expand traceback': 'Clique em uma coluna para expandir o log do erro',
'click to check for upgrades': 'clique aqui para checar por atualizações',
'click to open': 'clique para abrir',
'Client IP': 'IP do cliente',
'code': 'código',
'collapse/expand all': 'colapsar/expandir tudo',
'commit (mercurial)': 'commit (mercurial)',
'Compile': 'compilar',
'compiled application removed': 'aplicação compilada removida',
'Controllers': 'Controladores',
'controllers': 'controladores',
'Count': 'Contagem',
'Create': 'criar',
'create file with filename:': 'criar um arquivo com o nome:',
'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente',
'create new application:': 'nome da nova aplicação:',
'Create new simple application': 'Crie uma nova aplicação',
'Create/Upload': 'Create/Upload',
'created by': 'criado por',
'crontab': 'crontab',
'Current request': 'Requisição atual',
'Current response': 'Resposta atual',
'Current session': 'Sessão atual',
'currently running': 'Executando',
'currently saved or': 'Atualmente salvo ou',
'customize me!': 'Modifique-me',
'data uploaded': 'Dados enviados',
'database': 'banco de dados',
'database %s select': 'Seleção no banco de dados %s',
'database administration': 'administração de banco de dados',
'Date and Time': 'Data e Hora',
'db': 'db',
'Debug': 'Debug',
'defines tables': 'define as tabelas',
'Delete': 'Apague',
'delete': 'apagar',
'delete all checked': 'apagar marcados',
'delete plugin': 'apagar plugin',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Delete:': 'Apague:',
'Deploy': 'publicar',
'Deploy on Google App Engine': 'Publicar no Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Deploy to pythonanywhere': 'Deploy to pythonanywhere',
'Deploy to PythonAnywhere': 'Deploy to PythonAnywhere',
'Deployment Interface': 'Deployment Interface',
'Description': 'Descrição',
'design': 'modificar',
'DESIGN': 'Projeto',
'Design for': 'Projeto de',
'Detailed traceback description': 'Detailed traceback description',
'details': 'details',
'direction: ltr': 'direção: ltr',
'Disable': 'Disable',
'docs': 'docs',
'done!': 'feito!',
'download layouts': 'download layouts',
'Download layouts from repository': 'Download layouts from repository',
'download plugins': 'download plugins',
'Download plugins from repository': 'Download plugins from repository',
'E-mail': 'E-mail',
'EDIT': 'EDITAR',
'Edit': 'editar',
'Edit application': 'Editar aplicação',
'edit controller': 'editar controlador',
'Edit current record': 'Editar o registro atual',
'Edit Profile': 'Editar Perfil',
'edit views:': 'editar visões:',
'Editing %s': 'A Editar %s',
'Editing file': 'Editando arquivo',
'Editing file "%s"': 'Editando arquivo "%s"',
'Editing Language file': 'Editando arquivo de linguagem',
'Email Address': 'Email Address',
'Enterprise Web Framework': 'Framework web empresarial',
'Error': 'Erro',
'Error logs for "%(app)s"': 'Logs de erro para "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'erros',
'Exception instance attributes': 'Atributos da instancia de excessão',
'Exit Fullscreen': 'Sair de Ecrã Inteiro',
'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)',
'export as csv file': 'exportar como arquivo CSV',
'exposes': 'expõe',
'exposes:': 'exposes:',
'extends': 'estende',
'failed to reload module': 'Falha ao recarregar o módulo',
'failed to reload module because:': 'falha ao recarregar o módulo por:',
'File': 'Arquivo',
'file "%(filename)s" created': 'arquivo "%(filename)s" criado',
'file "%(filename)s" deleted': 'arquivo "%(filename)s" apagado',
'file "%(filename)s" uploaded': 'arquivo "%(filename)s" enviado',
'file "%(filename)s" was not deleted': 'arquivo "%(filename)s" não foi apagado',
'file "%s" of %s restored': 'arquivo "%s" de %s restaurado',
'file changed on disk': 'arquivo modificado no disco',
'file does not exist': 'arquivo não existe',
'file saved on %(time)s': 'arquivo salvo em %(time)s',
'file saved on %s': 'arquivo salvo em %s',
'filter': 'filtro',
'Find Next': 'Localizar Seguinte',
'Find Previous': 'Localizar Anterior',
'First name': 'Nome',
'Form has errors': 'Form has errors',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].',
'graph model': 'graph model',
'Group ID': 'ID do Grupo',
'Hello World': 'Olá Mundo',
'Help': 'ajuda',
'Hide/Show Translated strings': '',
'htmledit': 'htmledit',
'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.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.',
'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/Export': 'Importar/Exportar',
'includes': 'inclui',
'insert new': 'inserir novo',
'insert new %s': 'inserir novo %s',
'inspect attributes': 'inspecionar atributos',
'Install': 'instalar',
'Installed applications': 'Aplicações instaladas',
'internal error': 'erro interno',
'Internal State': 'Estado Interno',
'Invalid action': 'Ação inválida',
'Invalid email': 'E-mail inválido',
'invalid password': 'senha inválida',
'Invalid Query': 'Consulta inválida',
'invalid request': 'solicitação inválida',
'invalid ticket': 'ticket inválido',
'Keyboard shortcuts': 'Atalhos de teclado',
'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado',
'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados',
'languages': 'linguagens',
'Languages': 'Linguagens',
'languages updated': 'linguagens atualizadas',
'Last name': 'Sobrenome',
'Last saved on:': 'Salvo em:',
'License for': 'Licença para',
'lists by ticket': 'lists by ticket',
'Loading...': 'Loading...',
'loading...': 'carregando...',
'Local Apps': 'Local Apps',
'locals': 'locals',
'Login': 'Entrar',
'login': 'inicio de sessão',
'Login successful': 'Login successful',
'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
'Login/Register': 'Login/Register',
'Logout': 'finalizar sessão',
'Lost Password': 'Senha perdida',
'manage': 'gerenciar',
'Manage': 'Manage',
'merge': 'juntar',
'models': 'modelos',
'Models': 'Modelos',
'Modules': 'Módulos',
'modules': 'módulos',
'Name': 'Nome',
'new application "%s" created': 'nova aplicação "%s" criada',
'New Application Wizard': 'New Application Wizard',
'New application wizard': 'Assistente para novas aplicações ',
'new plugin installed': 'novo plugin instalado',
'New Record': 'Novo registro',
'new record inserted': 'novo registro inserido',
'New simple application': 'Nova aplicação básica',
'next 100 rows': 'próximos 100 registros',
'NO': 'NÃO',
'No databases in this application': 'Não existem bancos de dados nesta aplicação',
'no match': 'não encontrado',
'no package selected': 'nenhum pacote selecionado',
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'online designer': 'online designer',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Ou Obtenha do URL:',
'or import from csv file': 'ou importar de um arquivo CSV',
'or provide app url:': 'ou forneça a url de uma aplicação:',
'or provide application url:': 'ou forneça a url de uma aplicação:',
'Origin': 'Origem',
'Original/Translation': 'Original/Tradução',
'Overwrite installed app': 'sobrescrever aplicação instalada',
'Pack all': 'criar pacote',
'Pack compiled': 'criar pacote compilado',
'Pack custom': 'Pack custom',
'pack plugin': 'empacotar plugin',
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
'Password': 'Senha',
'password changed': 'senha alterada',
'Peeking at file': 'Visualizando arquivo',
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" eliminado',
'Plugin "%s" in application': 'Plugin "%s" na aplicação',
'plugins': 'plugins',
'Plugins': 'Plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Este site utiliza',
'previous 100 rows': '100 registros anteriores',
'Private files': 'Private files',
'private files': 'private files',
'PythonAnywhere Apps': 'PythonAnywhere Apps',
'PythonAnywhere Password': 'PythonAnywhere Password',
'Query:': 'Consulta:',
'Rapid Search': 'Rapid Search',
'Read': 'Read',
'record': 'registro',
'record does not exist': 'o registro não existe',
'record id': 'id do registro',
'Record ID': 'ID do Registro',
'Register': 'Registrar-se',
'Registration key': 'Chave de registro',
'Reload routes': 'Reload routes',
'Remove compiled': 'eliminar compilados',
'Replace': 'Substituir',
'Replace All': 'Substituir Tudo',
'request': 'request',
'requires python-git, but not installed': 'requires python-git, but not installed',
'Resolve Conflict file': 'Arquivo de resolução de conflito',
'response': 'response',
'restore': 'restaurar',
'revert': 'reverter',
'Role': 'Papel',
'Rows in table': 'Registros na tabela',
'Rows selected': 'Registros selecionados',
'rules are not defined': 'rules are not defined',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'A correr em %s',
'Save': 'Save',
'save': 'salvar',
'Save file:': 'Gravar ficheiro:',
'Save file: %s': 'Gravar ficheiro: %s',
'Save via Ajax': 'Gravar via Ajax',
'Saved file hash:': 'Hash do arquivo salvo:',
'selected': 'selecionado(s)',
'session': 'session',
'session expired': 'sessão expirada',
'shell': 'Terminal',
'Site': 'site',
'some files could not be removed': 'alguns arquicos não puderam ser removidos',
'Something went wrong please wait a few minutes before retrying': 'Something went wrong please wait a few minutes before retrying',
'source : filesystem': 'source : filesystem',
'Start a new app': 'Start a new app',
'Start searching': 'Start searching',
'Start wizard': 'iniciar assistente',
'state': 'estado',
'Static': 'Static',
'static': 'estáticos',
'Static files': 'Arquivos estáticos',
'Submit': 'Submit',
'submit': 'enviar',
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
'switch to : db': 'switch to : db',
'table': 'tabela',
'Table name': 'Nome da tabela',
'test': 'testar',
'Testing application': 'Testando a aplicação',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.',
'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates',
'There are no controllers': 'Não existem controllers',
'There are no models': 'Não existem modelos',
'There are no modules': 'Não existem módulos',
'There are no plugins': 'There are no plugins',
'There are no private files': '',
'There are no static files': 'Não existem arquicos estáticos',
'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada',
'There are no views': 'Não existem visões',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
'This is the %(filename)s template': 'Este é o template %(filename)s',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'Timestamp': 'Data Atual',
'TM': 'MR',
'to previous version.': 'para a versão anterior.',
'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle comment': 'Toggle comment',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'translation strings for the application': 'textos traduzidos para a aplicação',
'Translation strings for the application': 'Translation strings for the application',
'try': 'tente',
'try something like': 'tente algo como',
'Try the mobile interface': 'Try the mobile interface',
'Unable to check for upgrades': 'Não é possível checar as atualizações',
'unable to create application "%s"': 'não é possível criar a aplicação "%s"',
'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"',
'unable to delete file plugin "%(plugin)s"': 'não é possível criar o plugin "%(plugin)s"',
'Unable to download': 'Não é possível efetuar o download',
'Unable to download app': 'Não é possível baixar a aplicação',
'Unable to download app because:': 'Não é possível baixar a aplicação porque:',
'Unable to download because': 'Não é possível baixar porque',
'unable to parse csv file': 'não é possível analisar o arquivo CSV',
'unable to uninstall "%s"': 'não é possível instalar "%s"',
'unable to upgrade because "%s"': 'não é possível atualizar porque "%s"',
'uncheck all': 'desmarcar todos',
'Uninstall': 'desinstalar',
'update': 'atualizar',
'update all languages': 'atualizar todas as linguagens',
'Update:': 'Atualizar:',
'upgrade now to %s': 'upgrade now to %s',
'upgrade web2py now': 'atualize o web2py agora',
'upload': 'upload',
'Upload': 'Upload',
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
'Upload a package:': 'Faça upload de um pacote:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'Fazer upload de uma aplicação:',
'Upload existing application': 'Faça upload de uma aplicação existente',
'upload file:': 'Enviar arquivo:',
'upload plugin file:': 'Enviar arquivo de plugin:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para criar consultas mais complexas.',
'Use an url:': 'Use uma url:',
'User ID': 'ID do Usuario',
'Username': 'Username',
'variables': 'variáveis',
'Version': 'Versão',
'versioning': 'versionamento',
'Versioning': 'Versioning',
'view': 'visão',
'Views': 'Visões',
'views': 'visões',
'Warning!': 'Warning!',
'Web Framework': 'Web Framework',
'web2py Admin Password': 'web2py Admin Password',
'web2py is up to date': 'web2py está atualizado',
'web2py Recent Tweets': 'Tweets Recentes de @web2py',
'web2py upgraded; please restart it': 'web2py atualizado; favor reiniciar',
'Welcome to web2py': 'Bem-vindo ao web2py',
'YES': 'SIM',
'You only need these if you have already registered': 'You only need these if you have already registered',
}

File diff suppressed because one or more lines are too long

View File

@@ -75,8 +75,8 @@
* this over and over... all will be bound to the document
*/
/*adds btn class to buttons*/
$('button', target).addClass('btn');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
$('button:not([class^="btn"])', target).addClass('btn');
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn');
/* javascript for PasswordWidget*/
$('input[type=password][data-w2p_entropy]', target).each(function() {
web2py.validate_entropy($(this));
@@ -133,7 +133,7 @@
},
ajax_init: function(target) {
/*called whenever a fragment gets loaded */
$('.hidden', target).hide();
$('.w2p_hidden', target).hide();
web2py.manage_errors(target);
web2py.ajax_fields(target);
web2py.show_if_handler(target);
@@ -161,7 +161,7 @@
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.flash', function(e) {
doc.on('click', '.w2p_flash', function(e) {
var t = $(this);
if(t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
@@ -241,7 +241,7 @@
/*personally I don't like it.
*if there's an error it it flashed and can be removed
*as any other message
*doc.off('click', '.flash')
*doc.off('click', '.w2p_flash')
*/
switch(xhr.status) {
case 500:
@@ -257,12 +257,20 @@
if(form.hasClass('no_trap')) {
return;
}
form.attr('data-w2p_target', target);
var w2p_target = $(this).attr('data-w2p_target');
if (typeof w2p_target === typeof undefined || w2p_target === false) {
form.attr('data-w2p_target', target);
} else {
target = w2p_target;
}
var url = form.attr('action');
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
/* form has no action. Use component url. */
url = action;
}
form.submit(function(e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash();
@@ -530,13 +538,13 @@
},
/*helper for flash messages*/
flash: function(message, status) {
var flash = $('.flash');
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
if(flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
},
hide_flash: function() {
$('.flash').fadeOut(0).html('');
$('.w2p_flash').fadeOut(0).html('');
},
show_if_handler: function(target) {
var triggers = {};
@@ -692,7 +700,7 @@
$.web2py.component_handler(target);
},
main_hook: function() {
var flash = $('.flash');
var flash = $('.w2p_flash');
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);

View File

@@ -137,9 +137,9 @@
<h4>{{=T("Overview")}}</h4>
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
{{if total['entries'] > 0:}}
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses})",
dict(ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
</p>
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
dict( ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
</p>
<p>
{{=T("Size of cache:")}}
{{if object_stats:}}
@@ -155,8 +155,8 @@
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
</p>
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle();')}}
<div class="hidden" id="all_keys">
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="all_keys">
{{=total['keys']}}
</div>
<br />
@@ -183,8 +183,8 @@
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
</p>
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle();')}}
<div class="hidden" id="ram_keys">
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="ram_keys">
{{=ram['keys']}}
</div>
<br />
@@ -212,8 +212,8 @@
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
</p>
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle();')}}
<div class="hidden" id="disk_keys">
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="disk_keys">
{{=disk['keys']}}
</div>
<br />
@@ -249,8 +249,8 @@
<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>
<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 />

View File

@@ -1,5 +1,7 @@
{{extend 'layout.html'}}
{{
import re
regex_space = re.compile('\s+')
def all(items):
return reduce(lambda a,b:a and b,items,True)
def peekfile(path,file,vars={},title=None):
@@ -205,7 +207,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{=peekfile('views',c, dict(id=id))}}
</span>
<span class="extras celled celled-one">
{{if extend.has_key(c):}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
</span>
</li>
@@ -304,7 +306,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
while path!=file_path:
if len(file_path)>=len(path) and all([v==file_path[k] for k,v in enumerate(path)]):
path.append(file_path[len(path)])
thispath='static__'+'__'.join(path)
thispath = regex_space.sub('-', 'static__'+'__'.join(path))
}}
<li class="folder"><i>&nbsp;</i>
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>

View File

@@ -25,6 +25,7 @@
<form action="{{=URL(args=request.args)}}" method="POST">
<h2>{{=T('Select Files to Package')}}</h2>
<input type="submit" value="{{=T('Download .w2p')}}" class="btn"/>
<input type="submit" name="doexe" value="{{=T('Download as .exe')}}" class="btn"/>
<div style="margin-top:20px">
{{tree(base)}}
</div>

View File

@@ -144,7 +144,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{=peekfile('views',c)}}
</span>
<span class="extras celled">
{{if extend.has_key(c):}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
</span>
</li>

View File

@@ -27,7 +27,9 @@
{{buttons.append((URL('pack',args=a), T("Pack all")))}}
{{buttons.append((URL('pack_custom',args=a), T("Pack custom")))}}
{{if not os.path.exists('applications/%s/compiled' % a):}}
{{buttons.append((URL('compile_app',args=a), T("Compile")))}}
{{buttons.append((URL('compile_app',args=[a, 'skip_failed_views']),
T("Compile (skip failed views)")))}}
{{buttons.append((URL('compile_app',args=a), T("Compile (all or nothing)")))}}
{{else:}}
{{buttons.append((URL('pack',args=(a, 'compiled')), T("Pack compiled")))}}
{{if glob.glob('applications/%s/controllers/*.py' % a):}}
@@ -138,6 +140,7 @@
<p class="row-buttons">
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
{{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
</p>
</div> <!-- /DEPLOY ON GAE -->
<!-- APP WIZARD -->

View File

@@ -77,6 +77,7 @@
<script type="text/javascript">
jQuery(document).ready(function(){
jQuery("[rel=tooltip]").tooltip();
jQuery(":input").attr("autocomplete","off");
});
</script>
<script>

View File

@@ -0,0 +1,176 @@
{{extend 'layout.html'}}
<h2><span style="color:#139FD7">python</span>anywhere {{=T('Deployment Interface')}}</h2>
<div id="register_form">
<h3>{{=T('Login/Register')}}</h3>
<form class="form-horizontal" id="palogin">
<div class="control-group" id="username__row">
<label class="control-label" for="username">{{=T('Username')}}</label>
<div class="controls">
<input type="text" name="username" id="username"><span class="help-inline">*</span>
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="email_address__row">
<label class="control-label" for="email_address">{{=T('Email Address')}}</label>
<div class="controls">
<input type="text" name="email_address" id="email_address">
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="pythonanywhere_password__row">
<label class="control-label" for="pythonanywhere_password">{{=T('PythonAnywhere Password')}}</label>
<div class="controls">
<input type="password" name="pythonanywhere_password" id="pythonanywhere_password">
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="web2py_admin_password__row">
<label class="control-label" for="web2py_admin_password">{{=T('web2py Admin Password')}}</label>
<div class="controls">
<input type="password" name="web2py_admin_password" id="web2py_admin_password"><span class="help-inline">*</span>
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="accepts_terms__row">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="accepts_terms" id="accepts_terms"><a target="_blank" href="https://www.pythonanywhere.com/terms/">{{=T('Accept Terms')}}</a>
</label>
<span class="help-block"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn btn-primary" id="submit_palogin">{{=T('Submit')}}</button>
</div>
</div>
</form>
<p>* {{=T('You only need these if you have already registered')}}</p>
</div>
<div class="row-fluid" id="app_manager" style="display:none;">
<div class="span6">
<h3>{{=T('Local Apps')}}</h3>
<form id="apppicker">
<select name="apps" class="form-control" id="local" multiple>
<option>{{=T('Loading...')}}</option>
</select>
<input type="submit" value="Deploy" id="deploy_button" class="btn btn-primary">
</form>
<div class="alert alert-info">
<strong>{{=T('Warning!')}}</strong> {{=T('if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.')}}
</div>
</div>
<div class="span6">
<h3>{{=T('PythonAnywhere Apps')}}</h3>
<ul id="pythonanywhere">
<li>{{=T('Loading...')}}</li>
</ul>
</div>
</div>
<script>
$(document).ready(function() {
$('#palogin').off('submit');
$('#palogin').submit(function(event) {
var data = $('#palogin').serialize();
$.web2py.disableElement($('#submit_palogin'));
$.web2py.disableFormElements($('#palogin'));
$.ajax({
url: '{{=URL("pythonanywhere", "create_account")}}',
type: 'POST',
data: data,
dataType: 'json',
}).done(function(data, textStatus, jqXHR) {
$('#palogin .error').removeClass('error');
$('#palogin .help-block').text('');
if(data.status == 'error') {
for(var error in data.errors) {
$('#' + error + '__row').addClass('error');
$('#' + error + '__row .help-block').text(data.errors[error][0]);
}
$.web2py.enableElement($('#submit_palogin'));
$.web2py.enableFormElements($('#palogin'));
$.web2py.flash("{{=T('Form has errors')}}");
} else {
$.web2py.flash("{{=T('Login successful')}}");
$('#register_form').hide();
$('#app_manager').show();
refresh_apps();
}
}).fail(function(){
$.web2py.flash("{{=T('Something went wrong please wait a few minutes before retrying')}}");
$.web2py.enableElement($('#submit_palogin'));
$.web2py.enableFormElements($('#palogin'));
});
event.preventDefault();
});
$('#apppicker').off('submit');
$('#apppicker').submit(function(event) {
var data = $('#apppicker').serialize();
$.web2py.disableElement($('#deploy_button'));
$.ajax({
url: '{{=URL("pythonanywhere", "bulk_install")}}',
type: 'POST',
data: {username: $('#username').val(), password: $('#web2py_admin_password').val(), apps: $('#local').val()},
dataType: 'json',
}).done(function(data, textStatus, jqXHR) {
refresh_apps();
$.web2py.enableElement($('#deploy_button'));
}).fail(function(){
$.web2py.flash("{{=T('Something went wrong please wait a few minutes before retrying')}}");
$.web2py.enableElement($('#deploy_button'));
});
event.preventDefault();
});
});
function refresh_apps() {
// Refresh List of Apps
$('#deploy_button').prop('disabled', true);
$.ajax({
url: '{{=URL("pythonanywhere", "list_apps")}}',
type: 'GET',
data: {username: $('#username').val(), password: $('#web2py_admin_password').val()},
dataType: 'json',
}).done(function(data, textStatus, jqXHR) {
var i = 0;
$('#local').html('')
for(i = 0; i < data.local.length; i++) {
$('#local').append($('<option>', {
value: data.local[i],
text: data.local[i]
}));
}
$('#local').multiSelect('refresh');
$('#pythonanywhere').html('')
for(i = 0; i < data.pythonanywhere.length; i++) {
$('#pythonanywhere').append($('<li>', {
text: data.pythonanywhere[i]
}));
}
$('#deploy_button').prop('disabled', false);
$.web2py.hide_flash();
}).fail(function(){
// Mostly this happens if it's a new account, just waiting a bit should be enough.
$.get('http://' + $('#username').val() + '.pythonanywhere.com'); // Kickstart the instance
$.web2py.flash("{{=T('Please wait, giving pythonanywhere a moment...')}}");
setTimeout(refresh_apps, 30000);
});
}
</script>

View File

@@ -49,7 +49,8 @@ if request.function == 'manage':
auth.table_group(),
auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None
auth.requires_membership(manager_role)(lambda: None)()
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized")
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -80,7 +81,6 @@ if False and request.tickets_db:
def get_databases(request):
dbs = {}
for (key, value) in global_env.items():
cond = False
try:
cond = isinstance(value, GQLDB)
except:
@@ -420,7 +420,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -445,30 +445,31 @@ def ccache():
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
total.update(gae_stats)
else:
# get ram stats directly from the cache object
ram_stats = cache.ram.stats[request.application]
ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
ram['misses'] = ram_stats['misses']
try:
ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
except (KeyError, ZeroDivisionError):
ram['ratio'] = 0
for key, value in cache.ram.storage.iteritems():
if isinstance(value, dict):
ram['hits'] = value['hit_total'] - value['misses']
ram['misses'] = value['misses']
try:
ram['ratio'] = ram['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
ram['ratio'] = 0
else:
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
ram['entries'] += 1
if value[0] < ram['oldest']:
ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
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 isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
if isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
@@ -480,12 +481,12 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
total['objects'] = ram['objects'] + disk['objects']
total['hits'] = ram['hits'] + disk['hits']
total['misses'] = ram['misses'] + disk['misses']
total['keys'] = ram['keys'] + disk['keys']
ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
total[key] = ram[key] + disk[key]
try:
total['ratio'] = total['hits'] * 100 / (total['hits'] +
total['misses'])
@@ -575,11 +576,9 @@ def bg_graph_model():
meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))

View File

@@ -8,6 +8,8 @@
#### Learning and Demos
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
- [[web2py Reference Project http://www.web2pyref.com/]]
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)

View File

@@ -320,3 +320,10 @@ li.w2p_grid_breadcrumb_elem {
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
.control-label.readonly{
padding-top:0px !important;
padding-right:0px !important;
}

File diff suppressed because one or more lines are too long

View File

@@ -75,8 +75,8 @@
* this over and over... all will be bound to the document
*/
/*adds btn class to buttons*/
$('button', target).addClass('btn');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
$('button:not([class^="btn"])', target).addClass('btn');
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn');
/* javascript for PasswordWidget*/
$('input[type=password][data-w2p_entropy]', target).each(function() {
web2py.validate_entropy($(this));
@@ -133,7 +133,7 @@
},
ajax_init: function(target) {
/*called whenever a fragment gets loaded */
$('.hidden', target).hide();
$('.w2p_hidden', target).hide();
web2py.manage_errors(target);
web2py.ajax_fields(target);
web2py.show_if_handler(target);
@@ -161,7 +161,7 @@
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.flash', function(e) {
doc.on('click', '.w2p_flash', function(e) {
var t = $(this);
if(t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
@@ -241,7 +241,7 @@
/*personally I don't like it.
*if there's an error it it flashed and can be removed
*as any other message
*doc.off('click', '.flash')
*doc.off('click', '.w2p_flash')
*/
switch(xhr.status) {
case 500:
@@ -257,12 +257,20 @@
if(form.hasClass('no_trap')) {
return;
}
form.attr('data-w2p_target', target);
var w2p_target = $(this).attr('data-w2p_target');
if (typeof w2p_target === typeof undefined || w2p_target === false) {
form.attr('data-w2p_target', target);
} else {
target = w2p_target;
}
var url = form.attr('action');
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
/* form has no action. Use component url. */
url = action;
}
form.submit(function(e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash();
@@ -530,13 +538,13 @@
},
/*helper for flash messages*/
flash: function(message, status) {
var flash = $('.flash');
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
if(flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
},
hide_flash: function() {
$('.flash').fadeOut(0).html('');
$('.w2p_flash').fadeOut(0).html('');
},
show_if_handler: function(target) {
var triggers = {};
@@ -692,7 +700,7 @@
$.web2py.component_handler(target);
},
main_hook: function() {
var flash = $('.flash');
var flash = $('.w2p_flash');
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);

View File

@@ -137,9 +137,9 @@
<h4>{{=T("Overview")}}</h4>
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
{{if total['entries'] > 0:}}
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses})",
dict(ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
</p>
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
dict( ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
</p>
<p>
{{=T("Size of cache:")}}
{{if object_stats:}}
@@ -155,8 +155,8 @@
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
</p>
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle();')}}
<div class="hidden" id="all_keys">
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="all_keys">
{{=total['keys']}}
</div>
<br />
@@ -183,8 +183,8 @@
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
</p>
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle();')}}
<div class="hidden" id="ram_keys">
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="ram_keys">
{{=ram['keys']}}
</div>
<br />
@@ -212,8 +212,8 @@
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
</p>
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle();')}}
<div class="hidden" id="disk_keys">
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="disk_keys">
{{=disk['keys']}}
</div>
<br />
@@ -249,8 +249,8 @@
<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>
<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 />

View File

@@ -17,15 +17,19 @@
<ul>
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li>
<li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li>
<li><a target="_blank" href="http://www.10biosystems.com">10BioSystems</a> (USA)</li>
<li><a target="_blank" href="http://www.formatics.nl">Formatics</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li>
<li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li>
<li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li>
<li><a target="_blank" href="www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
<li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
<li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
<li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li>
<li><a target="_blank" href="http://i-am.pt">I am Consultoria</a> (Portugal)</li>
<li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li>
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
<li><a target="_blank" href="http://www.vsa-services.com/">VSA Services</a> (Singapore)</li>

View File

@@ -49,7 +49,8 @@ if request.function == 'manage':
auth.table_group(),
auth.table_permission()])
manager_role = manager_action.get('role', None) if manager_action else None
auth.requires_membership(manager_role)(lambda: None)()
if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)):
raise HTTP(403, "Not authorized")
menu = False
elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
@@ -80,7 +81,6 @@ if False and request.tickets_db:
def get_databases(request):
dbs = {}
for (key, value) in global_env.items():
cond = False
try:
cond = isinstance(value, GQLDB)
except:
@@ -420,7 +420,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -445,30 +445,31 @@ def ccache():
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
total.update(gae_stats)
else:
# get ram stats directly from the cache object
ram_stats = cache.ram.stats[request.application]
ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
ram['misses'] = ram_stats['misses']
try:
ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
except (KeyError, ZeroDivisionError):
ram['ratio'] = 0
for key, value in cache.ram.storage.iteritems():
if isinstance(value, dict):
ram['hits'] = value['hit_total'] - value['misses']
ram['misses'] = value['misses']
try:
ram['ratio'] = ram['hits'] * 100 / value['hit_total']
except (KeyError, ZeroDivisionError):
ram['ratio'] = 0
else:
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
ram['entries'] += 1
if value[0] < ram['oldest']:
ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
if hp:
ram['bytes'] += hp.iso(value[1]).size
ram['objects'] += hp.iso(value[1]).count
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 isinstance(value, dict):
disk['hits'] = value['hit_total'] - value['misses']
disk['misses'] = value['misses']
if isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
try:
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
except (KeyError, ZeroDivisionError):
disk['ratio'] = 0
else:
@@ -480,12 +481,12 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes']
total['objects'] = ram['objects'] + disk['objects']
total['hits'] = ram['hits'] + disk['hits']
total['misses'] = ram['misses'] + disk['misses']
total['keys'] = ram['keys'] + disk['keys']
ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
total[key] = ram[key] + disk[key]
try:
total['ratio'] = total['hits'] * 100 / (total['hits'] +
total['misses'])
@@ -575,11 +576,9 @@ def bg_graph_model():
meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group):
if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename)
else:
subgraphs[group]['tables'].append(tablename)
subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename))

View File

@@ -29,11 +29,12 @@ def user():
http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
http://..../[app]/default/user/bulk_register
use @auth.requires_login()
@auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id)
to decorate functions that need access control
also notice there is http://..../[app]/appadmin/manage/auth to allow administrator to manage users
"""
return dict(form=auth())

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
div.flash {
div.w2p_flash {
background-image: none;
border-radius: 4px;
-o-border-radius: 4px;
@@ -15,16 +15,18 @@ div.flash {
margin: 0 0 20px;
padding: 15px 35px 15px 15px;
}
div.flash.alert:hover {
div.w2p_flash.alert:hover {
opacity: 1;
}
.ie-lte8 div.flash {
.ie-lte8 div.w2p_flash {
filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0);
}
.ie-lte8 div.flash:hover {
.ie-lte8 div.w2p_flash:hover {
filter: alpha(opacity=25);
}
.main-container {
margin-top: 20px;
}
div.error {
width: auto;
@@ -35,7 +37,7 @@ div.error {
display: inline-block;
padding: 5px;
}
div.flash.alert {
div.w2p_flash.alert {
display: none;
position: fixed;
top: 70px;
@@ -134,7 +136,7 @@ header h1 {
header .jumbotron {
background-color: transparent;
}
.flash {
.w2p_flash {
opacity: 0.9!important;
right: 100px;
}
@@ -283,6 +285,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console .form-control {
width: 20%;
display: inline;
height: 100%;
}
.web2py_console #w2p_keywords {
width: 50%;
@@ -311,6 +314,3 @@ td.w2p_fc,
input[type=checkbox], input[type=radio] {
margin: 4px 4px 0 0;
}
.btn {
margin-right: 4px;
}

View File

@@ -33,7 +33,7 @@ audio {width:200px}
[type="text"], [type="password"], select {
margin-right: 5px; width: 300px;
}
.hidden {display:none;visibility:visible}
.w2p_hidden {display:none;visibility:visible}
.right {float:right; text-align:right}
.left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle}
@@ -88,7 +88,7 @@ div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; pa
#web2py_user_form td {vertical-align:top}
/*********** web2py specific ***********/
div.flash {
div.w2p_flash {
font-weight:bold;
display:none;
position:fixed;
@@ -117,11 +117,11 @@ div.flash {
z-index:2000;
}
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
div.w2p_flash #closeflash{color:inherit; float:right; margin-left:15px;}
.ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.flash:hover { opacity:0.25; }
div.w2p_flash:hover { opacity:0.25; }
div.error_wrapper {display:block}
div.error {
@@ -304,8 +304,8 @@ li.w2p_grid_breadcrumb_elem {
/* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie-lte8 div.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
.web2py_console .form-control {width: 20%; display: inline;}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,9 +6,9 @@
* this over and over... all will be bound to the document
*/
/*adds btn class to buttons*/
$('button', target).addClass('btn btn-default');
$('button:not([class^="btn"])', target).addClass('btn btn-default');
$("p.w2p-autocomplete-widget input").addClass('form-control');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn btn-default');
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn btn-default');
/* javascript for PasswordWidget*/
$('input[type=password][data-w2p_entropy]', target).each(function() {
web2py.validate_entropy($(this));
@@ -18,9 +18,9 @@
function pe(ul, e) {
var new_line = ml(ul);
rel(ul);
if ($(e.target).parent().is(':visible')) {
if ($(e.target).closest('li').is(':visible')) {
/* make sure we didn't delete the element before we insert after */
new_line.insertAfter($(e.target).parent());
new_line.insertAfter($(e.target).closest('li'));
} else {
/* the line we clicked on was deleted, just add to end of list */
new_line.appendTo(ul);
@@ -30,9 +30,9 @@
}
function rl(ul, e) {
if ($(ul).children().length > 1) {
if ($(ul).find('li').length > 1) {
/* only remove if we have more than 1 item so the list is never empty */
$(e.target).parent().remove();
$(e.target).closest('li').remove();
}
}
@@ -46,13 +46,13 @@
function rel(ul) {
/* keep only as many as needed*/
$(ul).find("li").each(function() {
var trimmed = $.trim($(this.firstChild).val());
var trimmed = $.trim($(this).find(":text").val());
if (trimmed == '') $(this).remove();
else $(this.firstChild).val(trimmed);
else $(this).find(":text").val(trimmed);
});
}
var ul = this;
$(ul).find(":text").after('<a class="btn btn-default" href="#">+</a>&nbsp;<a class="btn btn-default" href="#">-</a>').keypress(function(e) {
$(ul).find(":text").addClass('form-control').wrap("<div class='input-group'></div>").after('<div class="input-group-addon"><i class="glyphicon glyphicon-plus"></i></div><div class="input-group-addon"><i class="glyphicon glyphicon-minus"></i></div>').keypress(function(e) {
return (e.which == 13) ? pe(ul, e) : true;
}).next().click(function(e) {
pe(ul, e);

View File

@@ -75,8 +75,8 @@
* this over and over... all will be bound to the document
*/
/*adds btn class to buttons*/
$('button', target).addClass('btn');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
$('button:not([class^="btn"])', target).addClass('btn');
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn');
/* javascript for PasswordWidget*/
$('input[type=password][data-w2p_entropy]', target).each(function() {
web2py.validate_entropy($(this));
@@ -133,7 +133,7 @@
},
ajax_init: function(target) {
/*called whenever a fragment gets loaded */
$('.hidden', target).hide();
$('.w2p_hidden', target).hide();
web2py.manage_errors(target);
web2py.ajax_fields(target);
web2py.show_if_handler(target);
@@ -161,7 +161,7 @@
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.flash', function(e) {
doc.on('click', '.w2p_flash', function(e) {
var t = $(this);
if(t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
@@ -241,7 +241,7 @@
/*personally I don't like it.
*if there's an error it it flashed and can be removed
*as any other message
*doc.off('click', '.flash')
*doc.off('click', '.w2p_flash')
*/
switch(xhr.status) {
case 500:
@@ -257,12 +257,20 @@
if(form.hasClass('no_trap')) {
return;
}
form.attr('data-w2p_target', target);
var w2p_target = $(this).attr('data-w2p_target');
if (typeof w2p_target === typeof undefined || w2p_target === false) {
form.attr('data-w2p_target', target);
} else {
target = w2p_target;
}
var url = form.attr('action');
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
/* form has no action. Use component url. */
url = action;
}
form.submit(function(e) {
web2py.disableElement(form.find(web2py.formInputClickSelector));
web2py.hide_flash();
@@ -530,13 +538,13 @@
},
/*helper for flash messages*/
flash: function(message, status) {
var flash = $('.flash');
var flash = $('.w2p_flash');
web2py.hide_flash();
flash.html(message).addClass(status);
if(flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
},
hide_flash: function() {
$('.flash').fadeOut(0).html('');
$('.w2p_flash').fadeOut(0).html('');
},
show_if_handler: function(target) {
var triggers = {};
@@ -692,7 +700,7 @@
$.web2py.component_handler(target);
},
main_hook: function() {
var flash = $('.flash');
var flash = $('.w2p_flash');
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);

View File

@@ -137,9 +137,9 @@
<h4>{{=T("Overview")}}</h4>
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
{{if total['entries'] > 0:}}
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses})",
dict(ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
</p>
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
dict( ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
</p>
<p>
{{=T("Size of cache:")}}
{{if object_stats:}}
@@ -155,8 +155,8 @@
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
</p>
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle();')}}
<div class="hidden" id="all_keys">
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="all_keys">
{{=total['keys']}}
</div>
<br />
@@ -183,8 +183,8 @@
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
</p>
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle();')}}
<div class="hidden" id="ram_keys">
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="ram_keys">
{{=ram['keys']}}
</div>
<br />
@@ -212,8 +212,8 @@
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
</p>
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle();')}}
<div class="hidden" id="disk_keys">
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="w2p_hidden" id="disk_keys">
{{=disk['keys']}}
</div>
<br />
@@ -249,8 +249,8 @@
<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>
<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 />

View File

@@ -21,7 +21,6 @@
<meta name="google-site-verification" content="">
<!-- include stylesheets -->
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/bootstrap-theme.min.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.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')}}">
@@ -47,9 +46,9 @@
</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="flash alert alert-dismissable">{{=response.flash or ''}}</div>
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
<!-- Navbar ======================================= -->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<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">
@@ -75,7 +74,7 @@
{{end}}
<!-- Main ========================================= -->
<!-- Begin page content -->
<div class="container-fluid">
<div class="container-fluid main-container">
{{if left_sidebar_enabled:}}
<div class="col-md-3 left-sidebar">
{{block left_sidebar}}

25
appveyor.yml Normal file
View File

@@ -0,0 +1,25 @@
build: false
environment:
matrix:
- PYTHON: "C:/Python27"
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
clone_depth: 50
init:
- "ECHO %PYTHON%"
- set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
install:
- ps: Start-FileDownload https://bootstrap.pypa.io/get-pip.py
- python get-pip.py
- pip install codecov
- git submodule update --init --recursive
test_script:
- python web2py.py --run_system_tests --with_coverage
after_test:
- coverage combine
- codecov

150
fabfile.py vendored Normal file
View File

@@ -0,0 +1,150 @@
from fabric.api import *
from fabric.operations import put, get
from fabric.contrib.files import exists
import os
import datetime
import getpass
env.hosts = env.hosts or raw_input('hostname (example.com):').split(',')
env.user = env.user or raw_input('username :')
INSTALL_SCRIPT = "setup-web2py-nginx-uwsgi-ubuntu.sh"
now = datetime.datetime.now()
applications = '/home/www-data/web2py/applications'
def create_user(username):
"""fab -H root@host create_user:username"""
password = getpass.getpass(name+' password for %s> ' % username)
run('useradd -m %s' % username)
run('usermod --password %s %s' % (crypt.crypt(password, 'salt'), username))
run('mkdir -p ~%s/.ssh' % username)
run('cp /etc/sudoers /tmp/sudoers.new')
append('/tmp/sudoers.new', '%s ALL=NOPASSWD: ALL' % username, use_sudo=True)
run('visudo -c -f /tmp/sudoers.new')
run('EDITOR="cp /tmp/sudoers.new" visudo')
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
local('ssh-copy-id %s' % env.hosts[0])
def install_web2py():
"""fab -H username@host install_web2py"""
sudo('wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/%s' % INSTALL_SCRIPT)
sudo('chmod +x %s' % INSTALL_SCRIPT)
sudo('./'+INSTALL_SCRIPT)
def start_webserver():
sudo('service nginx start')
sudo('start uwsgi-emperor')
sudo('start web2py-scheduler')
def stop_webserver():
sudo('stop uwsgi-emperor')
sudo('service nginx stop')
sudo('stop web2py-scheduler')
def restart_webserver():
stop_webserver()
start_webserver()
def notify(appname=None):
"""fab -H username@host notify:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
with cd(appfolder):
sudo('echo "response.flash = \'System Going Down For Maintenance\'" > models/flash_goingdown.py')
def down(appname=None):
"""fab -H username@host down:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
with cd(appfolder):
sudo('echo `date` > DISABLED')
sudo('rm -rf sessions/* || true')
def up(appname=None):
"""fab -H username@host up:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
with cd(appfolder):
if exists('modules/flash_goingdown.py'):
sudo('rm modules/flash_goingdown.py')
sudo('rm DISABLED')
def mkdir_or_backup(appname):
appfolder = applications+'/'+appname
if not exists(appfolder):
sudo('mkdir %s' % appfolder)
sudo('chown -R www-data:www-data %s' % appfolder)
backup = None
else:
dt = now.strftime('%y-%m-%d-%h-%m')
backup = '%s.%s.zip' % (appname, dt)
with cd(applications):
sudo('zip -r %s %s' % (backup, appname))
return backup
def git_deploy(appname, repo):
"""fab -H username@host git_deploy:appname,username/remoname"""
appfolder = applications+'/'+appname
backup = mkdir_or_backup(appfolder)
if exists(appfolder):
with cd(appfolder):
sudo('git pull origin master')
sudo('chown -R www-data:www-data *')
else:
with cd(applications):
sudo('git clone git@github.com/%s %s' % (repo, name))
sudo('chown -R www-data:www-data %s' % name)
def retrieve(appname=None):
"""fab -H username@host retrieve:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
filename = '%s.zip' % appname
with cd(appfolder):
sudo('zip -r /tmp/%s *' % filename)
get('/tmp/%s' % filename, filename)
sudo('rm /tmp/%s' % filename)
local('unzip %s' % filename)
local('rm %s' % filename)
def deploy(appname=None, all=False):
"""fab -H username@host deploy:appname,all"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
if os.path.exists('_update.zip'):
os.unlink('_update.zip')
backup = mkdir_or_backup(appfolder)
if all=='all' or not backup:
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
else:
local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*')
put('_update.zip','/tmp/_update.zip')
with cd(appfolder):
sudo('unzip -o /tmp/_update.zip')
sudo('chown -R www-data:www-data *')
sudo('echo "%s" > DATE_DEPLOYMENT' % now)
if backup:
print 'TO RESTORE: fab restore:%s' % backup
def restore(backup):
"""fab -H username@host restore:backupfilename"""
appname = backup.split('/')[-1].split('.')[0]
appfolder = applications + '/' + appname
with cd(appfolder):
sudo('rm -r *')
with cd(applications):
sudo('unzip %s' % backup)
sudo('chown -R www-data:www-data %s' % appname)
def cleanup(appname):
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications + '/' + appname
with cd(appfolder):
sudo('rm -rf sessions/* || true')
sudo('rm -rf errors/* || true')
sudo('rm -rf cache/* || true')

View File

@@ -59,6 +59,8 @@ def app_pack(app, request, raise_ex=False, filenames=None):
w2p_pack(filename, apath(app, request), filenames=filenames)
return filename
except Exception, e:
import traceback
print traceback.format_exc()
if raise_ex:
raise
return False
@@ -119,9 +121,8 @@ def app_cleanup(app, request):
# Remove cache files
path = apath('%s/cache/' % app, request)
CacheOnDisk(folder=path).clear()
if os.path.exists(path):
CacheOnDisk(folder=path).clear()
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
@@ -130,7 +131,7 @@ def app_cleanup(app, request):
return r
def app_compile(app, request):
def app_compile(app, request, skip_failed_views=False):
"""Compiles the application
Args:
@@ -144,8 +145,8 @@ def app_compile(app, request):
from compileapp import compile_application, remove_compiled_application
folder = apath(app, request)
try:
compile_application(folder)
return None
failed_views = compile_application(folder, skip_failed_views)
return failed_views
except (Exception, RestrictedError):
tb = traceback.format_exc(sys.exc_info)
remove_compiled_application(folder)

View File

@@ -58,7 +58,7 @@ def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%)
old_mem = psutil.virtual_memory().percent
# if we have data in storage and utilization exceeds 90%
while storage and old_mem > percentage:
while storage and old_mem > percentage:
# removed oldest entry
storage.popitem(last=False)
# garbage collect
@@ -378,7 +378,7 @@ class CacheOnDisk(CacheAbstract):
def safe_apply(self, key, function, default_value=None):
"""
"""
Safely apply a function to the value of a key in storage and set
the return value of the function to it.
@@ -606,22 +606,26 @@ class Cache(object):
def wrapped_f():
if current.request.env.request_method != 'GET':
return func()
if quick:
session_ = True if 'S' in quick else False
vars_ = True if 'V' in quick else False
lang_ = True if 'L' in quick else False
user_agent_ = True if 'U' in quick else False
public_ = True if 'P' in quick else False
else:
(session_, vars_, lang_, user_agent_, public_) = \
(session, vars, lang, user_agent, public)
if time_expire:
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
if quick:
session_ = True if 'S' in quick else False
vars_ = True if 'V' in quick else False
lang_ = True if 'L' in quick else False
user_agent_ = True if 'U' in quick else False
public_ = True if 'P' in quick else False
else:
session_, vars_, lang_, user_agent_, public_ = session, vars, lang, user_agent, public
if not session_ and public_:
cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
else:
cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model:
#figure out the correct cache key
cache_key = [current.request.env.path_info, current.response.view]

View File

@@ -261,7 +261,7 @@ class LoadFactory(object):
import globals
target = target or 'c' + str(random.random())[2:]
attr['_id'] = target
request = self.environment['request']
request = current.request
if '.' in f:
f, extension = f.rsplit('.', 1)
if url or ajax:
@@ -464,22 +464,28 @@ def read_pyc(filename):
return marshal.loads(data[8:])
def compile_views(folder):
def compile_views(folder, skip_failed_views=False):
"""
Compiles all the views in the application specified by `folder`
"""
path = pjoin(folder, 'views')
failed_views = []
for fname in listdir(path, '^[\w/\-]+(\.\w+)*$'):
try:
data = parse_template(fname, path)
except Exception, e:
raise Exception("%s in %s" % (e, fname))
filename = 'views.%s.py' % fname.replace(os.path.sep, '.')
filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
if skip_failed_views:
failed_views.append(fname)
else:
raise Exception("%s in %s" % (e, fname))
else:
filename = ('views/%s.py' % fname).replace('/', '_').replace('\\', '_')
filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
return failed_views if failed_views else None
def compile_models(folder):
@@ -532,10 +538,11 @@ def run_models_in(environment):
It tries pre-compiled models first before compiling them.
"""
folder = environment['request'].folder
c = environment['request'].controller
request = current.request
folder = request.folder
c = request.controller
#f = environment['request'].function
response = environment['response']
response = current.response
path = pjoin(folder, 'models')
cpath = pjoin(folder, 'compiled')
@@ -577,7 +584,7 @@ def run_controller_in(controller, function, environment):
"""
# if compiled should run compiled!
folder = environment['request'].folder
folder = current.request.folder
path = pjoin(folder, 'compiled')
badc = 'invalid controller (%s/%s)' % (controller, function)
badf = 'invalid function (%s/%s)' % (controller, function)
@@ -631,7 +638,7 @@ def run_controller_in(controller, function, environment):
layer = filename + ':' + function
code = getcfs(layer, filename, lambda: compile2(code, layer))
restricted(code, environment, filename)
response = environment['response']
response = current.response
vars = response._vars
if response.postprocessing:
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
@@ -649,9 +656,9 @@ def run_view_in(environment):
or `view/generic.extension`
It tries the pre-compiled views_controller_function.pyc before compiling it.
"""
request = environment['request']
response = environment['response']
view = response.view
request = current.request
response = current.response
view = environment['response'].view
folder = request.folder
path = pjoin(folder, 'compiled')
badv = 'invalid view (%s)' % view
@@ -666,32 +673,28 @@ def run_view_in(environment):
ccode = parse_template(view, pjoin(folder, 'views'),
context=environment)
restricted(ccode, environment, 'file stream')
elif os.path.exists(path):
x = view.replace('/', '.')
files = ['views.%s.pyc' % x]
if allow_generic:
files.append('views.generic.%s.pyc' % request.extension)
# for backward compatibility
x = view.replace('/', '_')
files.append('views_%s.pyc' % x)
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
if request.extension == 'html':
files.append('views_%s.pyc' % x[:-5])
if allow_generic:
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
filename = pjoin(path, f)
if os.path.exists(filename):
code = read_pyc(filename)
restricted(code, environment, layer=filename)
return
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
else:
filename = pjoin(folder, 'views', view)
if os.path.exists(path): # compiled views
x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
is_compiled = os.path.exists(pjoin(path, files[0]))
# Don't use a generic view if the non-compiled view exists.
if is_compiled or (not is_compiled and not os.path.exists(filename)):
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
# for backward compatibility
if request.extension == 'html':
files.append('views_%s.pyc' % x[:-5])
if allow_generic:
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
compiled = pjoin(path, f)
if os.path.exists(compiled):
code = read_pyc(compiled)
restricted(code, environment, layer=compiled)
return
if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view)
@@ -725,7 +728,7 @@ def remove_compiled_application(folder):
pass
def compile_application(folder):
def compile_application(folder, skip_failed_views=False):
"""
Compiles all models, views, controller for the application in `folder`.
"""
@@ -733,7 +736,8 @@ def compile_application(folder):
os.mkdir(pjoin(folder, 'compiled'))
compile_models(folder)
compile_controllers(folder)
compile_views(folder)
failed_views = compile_views(folder, skip_failed_views)
return failed_views
def test():

View File

@@ -93,7 +93,7 @@ def video(url):
def googledoc_viewer(url):
return '<iframe src="http://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url)
return '<iframe src="https://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url)
def web2py_component(url):

View File

@@ -6,15 +6,17 @@
#
# Given the model
#
# db.define_table("table_name", Field("picture", "upload"), Field("thumbnail", "upload"))
# db.define_table("table_name", Field("picture", "upload"),
# Field("thumbnail", "upload"))
#
# # to resize the picture on upload
# to resize the picture on upload
#
# from images import RESIZE
#
# db.table_name.picture.requires = RESIZE(200, 200)
#
# # to store original image in picture and create a thumbnail in 'thumbnail' field
# to store original image in picture and create a thumbnail
# in 'thumbnail' field
#
# from images import THUMB
# db.table_name.thumbnail.compute = lambda row: THUMB(row.picture, 200, 200)
@@ -24,8 +26,11 @@ from gluon import current
class RESIZE(object):
def __init__(self, nx=160, ny=80, error_message=' image resize'):
(self.nx, self.ny, self.error_message) = (nx, ny, error_message)
def __init__(self, nx=160, ny=80, quality=100,
error_message=' image resize'):
(self.nx, self.ny, self.quality, self.error_message) = (
nx, ny, quality, error_message)
def __call__(self, value):
if isinstance(value, str) and len(value) == 0:
@@ -36,7 +41,7 @@ class RESIZE(object):
img = Image.open(value.file)
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
s = cStringIO.StringIO()
img.save(s, 'JPEG', quality=100)
img.save(s, 'JPEG', quality=self.quality)
s.seek(0)
value.file = s
except:
@@ -51,7 +56,7 @@ def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
request = current.request
from PIL import Image
import os
img = Image.open(os.path.join(request.folder,'uploads',image))
img = Image.open(os.path.join(request.folder, 'uploads', image))
img.thumbnail((nx, ny), Image.ANTIALIAS)
root, ext = os.path.splitext(image)
thumb = '%s_%s%s' % (root, name, ext)

View File

@@ -14,12 +14,20 @@ except Exception, e:
raise e
def ldap_auth(server='ldap', port=None,
def ldap_auth(server='ldap',
port=None,
base_dn='ou=users,dc=domain,dc=com',
mode='uid', secure=False,
cert_path=None, cert_file=None,
cacert_path=None, cacert_file=None, key_file=None,
bind_dn=None, bind_pw=None, filterstr='objectClass=*',
mode='uid',
secure=False,
self_signed_certificate=None, # See NOTE below
cert_path=None,
cert_file=None,
cacert_path=None,
cacert_file=None,
key_file=None,
bind_dn=None,
bind_pw=None,
filterstr='objectClass=*',
username_attrib='uid',
custom_scope='subtree',
allowed_groups=None,
@@ -33,6 +41,7 @@ def ldap_auth(server='ldap', port=None,
group_name_attrib='cn',
group_member_attrib='memberUid',
group_filterstr='objectClass=*',
tls=False,
logging_level='error'):
"""
@@ -80,6 +89,13 @@ def ldap_auth(server='ldap', port=None,
If ldap is using GnuTLS then you need cert_file="..." instead cert_path
because cert_path isn't implemented in GnuTLS :(
To enable TLS, set tls=True:
auth.settings.login_methods.append(ldap_auth(
server='my.ldap.server',
base_dn='ou=Users,dc=domain,dc=com',
tls=True))
If you need to bind to the directory with an admin account in order to
search it then specify bind_dn & bind_pw to use for this.
- currently only implemented for Active Directory
@@ -151,6 +167,14 @@ def ldap_auth(server='ldap', port=None,
You can set the logging level with the "logging_level" parameter, default
is "error" and can be set to error, warning, info, debug.
"""
if self_signed_certificate:
# NOTE : If you have a self-signed SSL Certificate pointing over "port=686" and "secure=True" alone
# will not work, you need also to set "self_signed_certificate=True".
# Ref1: https://onemoretech.wordpress.com/2015/06/25/connecting-to-ldap-over-self-signed-tls-with-python/
# Ref2: http://bneijt.nl/blog/post/connecting-to-ldaps-with-self-signed-cert-using-python/
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
logger = logging.getLogger('web2py.auth.ldap_auth')
if logging_level == 'error':
logger.setLevel(logging.ERROR)
@@ -188,8 +212,7 @@ def ldap_auth(server='ldap', port=None,
logger.warning('blank password not allowed')
return False
logger.debug('mode: [%s] manage_user: [%s] custom_scope: [%s]'
' manage_groups: [%s]' % (str(mode), str(manage_user),
str(custom_scope), str(manage_groups)))
' manage_groups: [%s]' % (str(mode), str(manage_user), str(custom_scope), str(manage_groups)))
if manage_user:
if user_firstname_attrib.count(':') > 0:
(user_firstname_attrib,
@@ -238,14 +261,10 @@ def ldap_auth(server='ldap', port=None,
# in the ldap_basedn
requested_attrs = ['sAMAccountName']
if manage_user:
requested_attrs.extend([user_firstname_attrib,
user_lastname_attrib,
user_mail_attrib])
requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
result = con.search_ext_s(
ldap_basedn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % (
ldap.filter.escape_filter_chars(username_bare),
filterstr),
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr),
requested_attrs)[0][1]
if not isinstance(result, dict):
# result should be a dict in the form
@@ -278,25 +297,21 @@ def ldap_auth(server='ldap', port=None,
if manage_user:
result = con.search_s(dn, ldap.SCOPE_BASE,
"(objectClass=*)",
[user_firstname_attrib,
user_lastname_attrib,
user_mail_attrib])[0][1]
[user_firstname_attrib, user_lastname_attrib, user_mail_attrib])[0][1]
if ldap_mode == 'uid':
# OpenLDAP (UID)
if ldap_binddn and ldap_bindpw:
con.simple_bind_s(ldap_binddn, ldap_bindpw)
dn = "uid=" + username + "," + ldap_basedn
dn = con.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, "(uid=%s)"%username, [''])[0][0]
dn = con.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, "(uid=%s)" % username, [''])[0][0]
else:
dn = "uid=" + username + "," + ldap_basedn
con.simple_bind_s(dn, password)
if manage_user:
result = con.search_s(dn, ldap.SCOPE_BASE,
"(objectClass=*)",
[user_firstname_attrib,
user_lastname_attrib,
user_mail_attrib])[0][1]
[user_firstname_attrib, user_lastname_attrib, user_mail_attrib])[0][1]
if ldap_mode == 'company':
# no DNs or password needed to search directory
@@ -311,9 +326,7 @@ def ldap_auth(server='ldap', port=None,
# find the uid
attrs = ['uid']
if manage_user:
attrs.extend([user_firstname_attrib,
user_lastname_attrib,
user_mail_attrib])
attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
# perform the actual search
company_search_result = con.search_s(ldap_basedn,
ldap.SCOPE_SUBTREE,
@@ -329,13 +342,11 @@ def ldap_auth(server='ldap', port=None,
basedns = ldap_basedn
else:
basedns = [ldap_basedn]
filter = '(&(uid=%s)(%s))' % (
ldap.filter.escape_filter_chars(username), filterstr)
filter = '(&(uid=%s)(%s))' % (ldap.filter.escape_filter_chars(username), filterstr)
found = False
for basedn in basedns:
try:
result = con.search_s(basedn, ldap.SCOPE_SUBTREE,
filter)
result = con.search_s(basedn, ldap.SCOPE_SUBTREE, filter)
if result:
user_dn = result[0][0]
# Check the password
@@ -344,9 +355,10 @@ def ldap_auth(server='ldap', port=None,
break
except ldap.LDAPError, detail:
(exc_type, exc_value) = sys.exc_info()[:2]
logger.warning(
"ldap_auth: searching %s for %s resulted in %s: %s\n" %
(basedn, filter, exc_type, exc_value)
logger.warning("ldap_auth: searching %s for %s resulted in %s: %s\n" % (basedn,
filter,
exc_type,
exc_value)
)
if not found:
logger.warning('User [%s] not found!' % username)
@@ -359,10 +371,7 @@ def ldap_auth(server='ldap', port=None,
basedns = ldap_basedn
else:
basedns = [ldap_basedn]
filter = '(&(%s=%s)(%s))' % (username_attrib,
ldap.filter.escape_filter_chars(
username),
filterstr)
filter = '(&(%s=%s)(%s))' % (username_attrib, ldap.filter.escape_filter_chars(username), filterstr)
if custom_scope == 'subtree':
ldap_scope = ldap.SCOPE_SUBTREE
elif custom_scope == 'base':
@@ -381,9 +390,10 @@ def ldap_auth(server='ldap', port=None,
break
except ldap.LDAPError, detail:
(exc_type, exc_value) = sys.exc_info()[:2]
logger.warning(
"ldap_auth: searching %s for %s resulted in %s: %s\n" %
(basedn, filter, exc_type, exc_value)
logger.warning("ldap_auth: searching %s for %s resulted in %s: %s\n" % (basedn,
filter,
exc_type,
exc_value)
)
if not found:
logger.warning('User [%s] not found!' % username)
@@ -393,16 +403,14 @@ def ldap_auth(server='ldap', port=None,
logger.info('[%s] Manage user data' % str(username))
try:
if user_firstname_part is not None:
store_user_firstname = result[user_firstname_attrib][
0].split(' ', 1)[user_firstname_part]
store_user_firstname = result[user_firstname_attrib][0].split(' ', 1)[user_firstname_part]
else:
store_user_firstname = result[user_firstname_attrib][0]
except KeyError, e:
store_user_firstname = None
try:
if user_lastname_part is not None:
store_user_lastname = result[user_lastname_attrib][
0].split(' ', 1)[user_lastname_part]
store_user_lastname = result[user_lastname_attrib][0].split(' ', 1)[user_lastname_part]
else:
store_user_lastname = result[user_lastname_attrib][0]
except KeyError, e:
@@ -411,32 +419,26 @@ def ldap_auth(server='ldap', port=None,
store_user_mail = result[user_mail_attrib][0]
except KeyError, e:
store_user_mail = None
try:
#
update_or_insert_values = {'first_name': store_user_firstname,
'last_name': store_user_lastname,
'email': store_user_mail}
if '@' not in username:
# user as username
# #################
# ################
fields = ['first_name', 'last_name', 'email']
user_in_db = db(db.auth_user.username == username)
if user_in_db.count() > 0:
user_in_db.update(first_name=store_user_firstname,
last_name=store_user_lastname,
email=store_user_mail)
else:
db.auth_user.insert(first_name=store_user_firstname,
last_name=store_user_lastname,
email=store_user_mail,
username=username)
except:
#
elif '@' in username:
# user as email
# ##############
# #############
fields = ['first_name', 'last_name']
user_in_db = db(db.auth_user.email == username)
if user_in_db.count() > 0:
user_in_db.update(first_name=store_user_firstname,
last_name=store_user_lastname)
else:
db.auth_user.insert(first_name=store_user_firstname,
last_name=store_user_lastname,
email=username)
update_or_insert_values = {f: update_or_insert_values[f] for f in fields}
if user_in_db.count() > 0:
actual_values = user_in_db.select(*[db.auth_user[f] for f in fields]).first().as_dict()
if update_or_insert_values != actual_values: # We don't update record if values are the same
user_in_db.update(**update_or_insert_values)
else:
db.auth_user.insert(**update_or_insert_values)
con.unbind()
if manage_groups:
@@ -478,9 +480,7 @@ def ldap_auth(server='ldap', port=None,
# No match
return False
def do_manage_groups(username,
password=None,
db=db):
def do_manage_groups(username, password=None, db=db):
"""
Manage user groups
@@ -500,23 +500,19 @@ def ldap_auth(server='ldap', port=None,
# Get all group name where the user is in actually in local db
# #############################################################
try:
db_user_id = db(db.auth_user.username == username).select(
db.auth_user.id).first().id
db_user_id = db(db.auth_user.username == username).select(db.auth_user.id).first().id
except:
try:
db_user_id = db(db.auth_user.email == username).select(
db.auth_user.id).first().id
db_user_id = db(db.auth_user.email == username).select(db.auth_user.id).first().id
except AttributeError, e:
#
# There is no user in local db
# We create one
# ##############################
try:
db_user_id = db.auth_user.insert(username=username,
first_name=username)
db_user_id = db.auth_user.insert(username=username, first_name=username)
except AttributeError, e:
db_user_id = db.auth_user.insert(email=username,
first_name=username)
db_user_id = db.auth_user.insert(email=username, first_name=username)
if not db_user_id:
logging.error(
'There is no username or email for %s!' % username)
@@ -524,27 +520,23 @@ def ldap_auth(server='ldap', port=None,
# if old pydal version, assume this is a relational database which can do joins
db_can_join = db.can_join() if hasattr(db, 'can_join') else True
if db_can_join:
db_group_search = db(
(db.auth_membership.user_id == db_user_id) &
(db.auth_user.id == db.auth_membership.user_id) &
(db.auth_group.id == db.auth_membership.group_id))
db_group_search = \
db((db.auth_membership.user_id == db_user_id) &
(db.auth_user.id == db.auth_membership.user_id) &
(db.auth_group.id == db.auth_membership.group_id))
else:
# no joins on NoSQL databases, perform two queries
db_group_search = db(db.auth_membership.user_id == db_user_id)
group_ids = [x.group_id for x in db_group_search.select(
db.auth_membership.group_id, distinct=True)]
group_ids = [x.group_id for x in db_group_search.select(db.auth_membership.group_id, distinct=True)]
db_group_search = db(db.auth_group.id.belongs(group_ids))
db_groups_of_the_user = list()
db_group_id = dict()
if db_group_search.count() > 0:
for group in db_group_search.select(db.auth_group.id,
db.auth_group.role,
distinct=True):
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)))
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
#
# Delete user membership from groups where user is not anymore
@@ -552,8 +544,7 @@ def ldap_auth(server='ldap', port=None,
for group_to_del in db_groups_of_the_user:
if ldap_groups_of_the_user.count(group_to_del) == 0:
db((db.auth_membership.user_id == db_user_id) &
(db.auth_membership.group_id == \
db_group_id[group_to_del])).delete()
(db.auth_membership.group_id == db_group_id[group_to_del])).delete()
#
# Create user membership in groups where user is not in already
@@ -561,16 +552,12 @@ def ldap_auth(server='ldap', port=None,
for group_to_add in ldap_groups_of_the_user:
if db_groups_of_the_user.count(group_to_add) == 0:
if db(db.auth_group.role == group_to_add).count() == 0:
gid = db.auth_group.insert(role=group_to_add,
description='Generated from LDAP')
gid = db.auth_group.insert(role=group_to_add, description='Generated from LDAP')
else:
gid = db(db.auth_group.role == group_to_add).select(
db.auth_group.id).first().id
db.auth_membership.insert(user_id=db_user_id,
group_id=gid)
gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id
db.auth_membership.insert(user_id=db_user_id, group_id=gid)
except:
logger.warning("[%s] Groups are not managed successfully!" %
str(username))
logger.warning("[%s] Groups are not managed successfully!" % str(username))
import traceback
logger.debug(traceback.format_exc())
return False
@@ -610,6 +597,8 @@ def ldap_auth(server='ldap', port=None,
ldap_port = 389
con = ldap.initialize(
"ldap://" + ldap_server + ":" + str(ldap_port))
if tls:
con.start_tls_s()
return con
def get_user_groups_from_ldap(username,
@@ -659,10 +648,12 @@ def ldap_auth(server='ldap', port=None,
con.simple_bind_s(username, password)
logger.debug('Ldap username connect...')
# We have to use the full string
username = con.search_ext_s(base_dn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" %
(ldap.filter.escape_filter_chars(username_bare),
filterstr), ["cn"])[0][0]
username = \
con.search_ext_s(base_dn,
ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare),
filterstr),
["cn"])[0][0]
else:
if ldap_binddn:
# need to search directory with an bind_dn account 1st
@@ -675,18 +666,14 @@ def ldap_auth(server='ldap', port=None,
if username is None:
return list()
# search for groups where user is in
filter = '(&(%s=%s)(%s))' % (ldap.filter.escape_filter_chars(
group_member_attrib
),
filter = '(&(%s=%s)(%s))' % (ldap.filter.escape_filter_chars(group_member_attrib),
ldap.filter.escape_filter_chars(username),
group_filterstr)
group_search_result = con.search_s(group_dn,
ldap.SCOPE_SUBTREE,
filter, [group_name_attrib])
group_search_result = con.search_s(group_dn, ldap.SCOPE_SUBTREE, filter, [group_name_attrib])
ldap_groups_of_the_user = list()
for group_row in group_search_result:
group = group_row[1]
if type(group) == dict and group.has_key(group_name_attrib):
if type(group) == dict and group_name_attrib in group:
ldap_groups_of_the_user.extend(group[group_name_attrib])
con.unbind()

View File

@@ -139,24 +139,36 @@ server for requests. It can be used for the optional"scope" parameters for Face
Return the access token generated by the authenticating server.
If token is already in the session that one will be used.
If token has expired refresh_token is used to get another token.
Otherwise the token is fetched from the auth server.
"""
refresh_token = None
if current.session.token and 'expires' in current.session.token:
expires = current.session.token['expires']
# reuse token until expiration
if expires == 0 or expires > time.time():
return current.session.token['access_token']
return current.session.token['access_token']
if 'refresh_token' in current.session.token:
refresh_token = current.session.token['refresh_token']
code = current.request.vars.code
if code:
data = dict(client_id=self.client_id,
client_secret=self.client_secret,
redirect_uri=current.session.redirect_uri,
code=code,
grant_type='authorization_code'
)
if code or refresh_token:
data = dict(
client_id=self.client_id,
client_secret=self.client_secret,
)
if code:
data.update(
redirect_uri=current.session.redirect_uri,
code=code,
grant_type='authorization_code'
)
elif refresh_token:
data.update(
refresh_token=refresh_token,
grant_type='refresh_token'
)
open_url = None
opener = self.__build_url_opener(self.token_url)

View File

@@ -13,6 +13,7 @@ Include in your model (eg db.py)::
auth.define_tables(username=True)
from gluon.contrib.login_methods.saml2_auth import Saml2Auth
import os
auth.settings.login_form=Saml2Auth(
config_file = os.path.join(request.folder,'private','sp_conf'),
maps=dict(
@@ -20,10 +21,59 @@ Include in your model (eg db.py)::
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]))
you must have private/sp_conf.py, the pysaml2 sp configuration file
you must have private/sp_conf.py, the pysaml2 sp configuration file. For example:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
import os.path
import requests
import tempfile
BASEDIR = os.path.abspath(os.path.dirname(__file__))
# Web2py SP url and application name
HOST = 'http://127.0.0.1:8000'
APP = 'sp'
# To load the IDP metadata...
IDP_METADATA = 'http://127.0.0.1:8088/metadata'
def full_path(local_file):
return os.path.join(BASEDIR, local_file)
CONFIG = {
# your entity id, usually your subdomain plus the url to the metadata view.
'entityid': '%s/%s/default/metadata' % (HOST, APP),
'service': {
'sp' : {
'name': 'MYSP',
'endpoints': {
'assertion_consumer_service': [
('%s/%s/default/user/login' % (HOST, APP), BINDING_HTTP_REDIRECT),
('%s/%s/default/user/login' % (HOST, APP), BINDING_HTTP_POST),
],
},
},
},
# Your private and public key.
'key_file': full_path('pki/mykey.pem'),
'cert_file': full_path('pki/mycert.pem'),
# where the remote metadata is stored
'metadata': {
"remote": [{
"url": IDP_METADATA,
"cert":full_path('pki/mycert.pem')
}]
},
}
"""
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
from saml2.client import Saml2Client
from gluon.utils import web2py_uuid
from gluon import current, redirect, URL
@@ -59,10 +109,13 @@ def saml2_handler(session, request, config_filename = None):
client = Saml2Client(config_file = config_filename)
idps = client.metadata.with_descriptor("idpsso")
entityid = idps.keys()[0]
bindings = [BINDING_HTTP_REDIRECT]
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
binding, destination = client.pick_binding(
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
binding = BINDING_HTTP_REDIRECT
if request.env.request_method == 'GET':
binding = BINDING_HTTP_REDIRECT
elif request.env.request_method == 'POST':
binding = BINDING_HTTP_POST
if not request.vars.SAMLResponse:
req_id, req = client.create_authn_request(destination, binding=binding)
relay_state = web2py_uuid().replace('-','')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf8 -*-
# -*- coding: utf-8 -*-
# Plural-Forms for fr (French))
nplurals=2 # French language has 2 forms:
@@ -15,3 +15,55 @@ get_plural_id = lambda n: int(n != 1)
# for words (or phrases) not found in plural_dict dictionary
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
irregular={
'aïeul': 'aïeux',
'bonhomme': 'bonshommes',
'ciel': 'cieux',
'oeil': 'yeux',
'œil': 'yeux',
'madame': 'mesdames',
'mademoiselle': 'mesdemoiselles',
'monsieur': 'messieurs',
'bijou': 'bijoux',
'caillou': 'cailloux',
'chou': 'choux',
'genou': 'genoux',
'hibou': 'hiboux',
'joujou': 'joujoux',
'pou': 'poux',
'corail': ' coraux',
'émail': 'émaux',
'travail': 'travaux',
'vitrail': 'vitraux',
'soupirail': 'soupiraux',
'bail': 'baux',
'fermail': 'fermaux',
'ventail': 'ventaux',
'bleu': 'bleus',
'pneu': 'pneus',
'émeu': 'émeus',
'enfeu': 'enfeus',
#'lieu': 'lieus', # poisson
}
def construct_plural_form(word, plural_id):
u"""
>>> [construct_plural_form(x, 1) for x in \
[ 'bleu', 'nez', 'sex', 'bas', 'gruau', 'jeu', 'journal',\
'chose' ]]
['bleus', 'nez', 'sex', 'bas', 'gruaux', 'jeux', 'journaux', 'choses']
"""
if word in irregular:
return irregular[word]
if word[-1:] in ('s', 'x', 'z'):
return word
if word[-2:] in ('au', 'eu'):
return word + 'x'
if word[-2:] == 'al':
return word[0:-2] + 'aux'
return word + 's'
if __name__ == '__main__':
import doctest
doctest.testmod()

View File

@@ -33,7 +33,7 @@ except ImportError:
class JSONRPCError(RuntimeError):
"Error object for remote procedure call fail"
def __init__(self, code, message, data=None):
def __init__(self, code, message, data=''):
value = "%s: %s\n%s" % (code, message, '\n'.join(data))
RuntimeError.__init__(self, value)
self.code = code

View File

@@ -16,7 +16,7 @@ def quote(text):
class Node:
def __init__(self, name, value, url='.', readonly=False, active=True,
onchange=None, **kwarg):
onchange=None, select=False, size=4, **kwarg):
self.url = url
self.name = name
self.value = str(value)
@@ -26,11 +26,21 @@ class Node:
self.readonly = readonly
self.active = active
self.onchange = onchange
self.size = 4
self.size = size
self.locked = False
self.select = value if select and not isinstance(value, str) else False
def xml(self):
return """<input name="%s" id="%s" value="%s" size="%s"
if self.select:
selectAttributes = dict(_name=self.name,_id=self.name,_size=self.size,
_onblur="ajax('%s/blur',['%s']);"%(self.url,self.name))
# _onkeyup="ajax('%s/keyup',['%s'], ':eval');"%(self.url,self.name),
# _onfocus="ajax('%s/focus',['%s'], ':eval');"%(self.url,self.name),
for k,v in selectAttributes.items():
self.select[k] = v
return self.select.xml()
else:
return """<input name="%s" id="%s" value="%s" size="%s"
onkeyup="ajax('%s/keyup',['%s'], ':eval');"
onfocus="ajax('%s/focus',['%s'], ':eval');"
onblur="ajax('%s/blur',['%s'], ':eval');" %s/>
@@ -391,7 +401,8 @@ class Sheet:
def __init__(self, rows, cols, url='.', readonly=False,
active=True, onchange=None, value=None, data=None,
headers=None, update_button="", **kwarg):
headers=None, update_button="", c_headers=None,
r_headers=None, **kwarg):
"""
Arguments:
@@ -425,6 +436,9 @@ class Sheet:
self.tr_attributes = {}
self.td_attributes = {}
self.c_headers = c_headers
self.r_headers = r_headers
self.data = data
self.readonly = readonly
@@ -505,7 +519,7 @@ class Sheet:
self.environment[name] = obj
def cell(self, key, value, readonly=False, active=True,
onchange=None, **kwarg):
onchange=None, select=False, **kwarg):
"""
key is the name of the cell
value is the initial value of the cell. It can be a formula "=1+3"
@@ -528,7 +542,7 @@ class Sheet:
value = value(r, c)
node = Node(key, value, self.url, readonly, active,
onchange, **kwarg)
onchange, select=select, **kwarg)
self.nodes[key] = node
self[key] = value
@@ -781,11 +795,19 @@ class Sheet:
gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT)
regex = re.compile('r\d+c\d+')
header = TR(TH(), *[TH('c%s' % c)
if not self.c_headers:
header = TR(TH(), *[TH('c%s' % c)
for c in range(self.cols)])
else:
header = TR(TH(), *[TH('%s' % c)
for c in self.c_headers])
rows = []
for r in range(self.rows):
tds = [TH('r%s' % r), ]
if not self.r_headers:
tds = [TH('r%s' % r), ]
else:
tds = [TH('%s' % self.r_headers[r]), ]
for c in range(self.cols):
key = 'r%sc%s' % (r, c)
attributes = {"_class": "w2p_spreadsheet_col_%s" %

View File

@@ -145,6 +145,10 @@ class TokenHandler(tornado.web.RequestHandler):
class DistributeHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
def open(self, params):
group, token, name = params.split('/') + [None, None]
self.group = group or 'default'

View File

@@ -41,7 +41,7 @@ class CustomImportException(ImportError):
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
"""
web2py's custom importer. It behaves like the standard Python importer but
web2py's custom importer. It behaves like the standard Python importer but
it tries to transform import statements as something like
"import applications.app_name.modules.x".
If the import fails, it falls back on naive_importer
@@ -80,7 +80,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
if not fromlist:
# import like "import x" or "import x.y"
result = None
for itemname in name.split("."):
for itemname in name.split("."):
new_mod = base_importer(
modules_prefix, globals, locals, [itemname], level)
try:

View File

@@ -208,7 +208,7 @@ class Request(Storage):
def parse_get_vars(self):
"""Takes the QUERY_STRING and unpacks it to get_vars
"""
query_string = self.env.get('QUERY_STRING', '')
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
get_vars = self._get_vars = Storage(dget)
for (key, value) in get_vars.iteritems():
@@ -1023,10 +1023,16 @@ class Session(Storage):
def _fixup_before_save(self):
response = current.response
rcookies = response.cookies
if self._forget and response.session_id_name in rcookies:
scookies = rcookies.get(response.session_id_name)
if not scookies:
return
if self._forget:
del rcookies[response.session_id_name]
elif self._secure and response.session_id_name in rcookies:
rcookies[response.session_id_name]['secure'] = True
return
if self.get('httponly_cookies',True):
scookies['HttpOnly'] = True
if self._secure:
scookies['secure'] = True
def clear_session_cookies(self):
request = current.request
@@ -1074,6 +1080,7 @@ class Session(Storage):
if response.session_storage_type == 'file':
target = recfile.generate(response.session_filename)
try:
self._close(response)
os.unlink(target)
except:
pass

View File

@@ -668,7 +668,7 @@ class XML(XmlComponent):
def XML_unpickle(data):
return marshal.loads(data)
return XML(marshal.loads(data))
def XML_pickle(data):
@@ -784,6 +784,9 @@ class DIV(XmlComponent):
else:
return self.components[i]
def get(self, i):
return self.attributes.get(i)
def __setitem__(self, i, value):
"""
Sets attribute with name 'i' or component #i.
@@ -1135,7 +1138,7 @@ class DIV(XmlComponent):
for (key, value) in kargs.iteritems():
if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)):
if self[key] != str(value):
if str(self[key]) != str(value):
check = False
elif key in self.attributes:
if not value.search(str(self[key])):
@@ -2643,7 +2646,7 @@ def test():
>>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
>>> isinstance(form.as_dict(), dict)
True
>>> form.as_dict(flat=True).has_key("vars")
>>> "vars" in form.as_dict(flat=True)
True
>>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
True

View File

@@ -370,8 +370,8 @@ def wsgibase(environ, responder):
cid = env.http_web2py_component_element,
is_local = (env.remote_addr in local_hosts and
client == env.remote_addr),
is_shell = cmd_opts and cmd_opts.shell,
is_sheduler = cmd_opts and cmd_opts.scheduler,
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'
@@ -423,10 +423,13 @@ def wsgibase(environ, responder):
# ##################################################
if env.http_cookie:
try:
request.cookies.load(env.http_cookie)
except Cookie.CookieError, e:
pass # invalid cookies
for single_cookie in env.http_cookie.split(';'):
single_cookie = single_cookie.strip()
if single_cookie:
try:
request.cookies.load(single_cookie)
except Cookie.CookieError:
pass # single invalid cookie ignore
# ##################################################
# try load session or create new session file

View File

@@ -119,7 +119,7 @@ class LockedFile(object):
lock(self.file, LOCK_EX)
if not 'a' in mode:
self.file.seek(0)
self.file.truncate()
self.file.truncate(0)
else:
raise RuntimeError("invalid LockedFile(...,mode)")

View File

@@ -66,14 +66,15 @@ class XssCleaner(HTMLParser):
#to strip or escape disallowed tags?
self.strip_disallowed = strip_disallowed
self.in_disallowed = False
# there might be data after final closing tag, that is to be ignored
self.in_disallowed = [False]
def handle_data(self, data):
if data and not self.in_disallowed:
if data and not self.in_disallowed[-1]:
self.result += xssescape(data)
def handle_charref(self, ref):
if self.in_disallowed:
if self.in_disallowed[-1]:
return
elif len(ref) < 7 and (ref.isdigit() or ref == 'x27'): # x27 is a special case for apostrophe
self.result += '&#%s;' % ref
@@ -81,7 +82,7 @@ class XssCleaner(HTMLParser):
self.result += xssescape('&#%s' % ref)
def handle_entityref(self, ref):
if self.in_disallowed:
if self.in_disallowed[-1]:
return
elif ref in entitydefs:
self.result += '&%s;' % ref
@@ -89,7 +90,7 @@ class XssCleaner(HTMLParser):
self.result += xssescape('&%s' % ref)
def handle_comment(self, comment):
if self.in_disallowed:
if self.in_disallowed[-1]:
return
elif comment:
self.result += xssescape('<!--%s-->' % comment)
@@ -100,11 +101,11 @@ class XssCleaner(HTMLParser):
attrs
):
if tag not in self.permitted_tags:
if self.strip_disallowed:
self.in_disallowed = True
else:
self.in_disallowed.append(True)
if (not self.strip_disallowed):
self.result += xssescape('<%s>' % tag)
else:
self.in_disallowed.append(False)
bt = '<' + tag
if tag in self.allowed_attributes:
attrs = dict(attrs)
@@ -119,6 +120,7 @@ class XssCleaner(HTMLParser):
else:
bt += ' %s=%s' % (xssescape(attribute),
quoteattr(attrs[attribute]))
# deal with <a> without href and <img> without src
if bt == '<a' or bt == '<img':
return
if tag in self.requires_no_close:
@@ -129,10 +131,9 @@ class XssCleaner(HTMLParser):
def handle_endtag(self, tag):
bracketed = '</%s>' % tag
self.in_disallowed.pop()
if tag not in self.permitted_tags:
if self.strip_disallowed:
self.in_disallowed = False
else:
if (not self.strip_disallowed):
self.result += xssescape(bracketed)
elif tag in self.open_tags:
self.result += bracketed
@@ -143,10 +144,13 @@ class XssCleaner(HTMLParser):
Accepts relative, absolute, and mailto urls
"""
parsed = urlparse(url)
return (parsed[0] in self.allowed_schemes and '.' in parsed[1]) \
or (parsed[0] in self.allowed_schemes and '@' in parsed[2]) \
or (parsed[0] == '' and parsed[2].startswith('/'))
if url.startswith('#'):
return True
else:
parsed = urlparse(url)
return ((parsed[0] in self.allowed_schemes and '.' in parsed[1]) or
(parsed[0] in self.allowed_schemes and '@' in parsed[2]) or
(parsed[0] == '' and parsed[2].startswith('/')))
def strip(self, rawstring, escape=True):
"""

View File

@@ -129,6 +129,8 @@ def env(
if global_settings.cmd_options:
ip = global_settings.cmd_options.ip
port = global_settings.cmd_options.port
request.is_shell = global_settings.cmd_options.shell is not None
request.is_scheduler = global_settings.cmd_options.scheduler is not None
else:
ip, port = '127.0.0.1', '8000'
request.env.http_host = '%s:%s' % (ip, port)

View File

@@ -29,7 +29,7 @@ from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from pydal.base import DEFAULT
from pydal.objects import Table, Row, Expression, Field
from pydal.adapters.base import CALLABLETYPES
from pydal.helpers.methods import smart_query, bar_encode
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
from pydal.helpers.classes import Reference, SQLCustomType
from gluon.storage import Storage
from gluon.utils import md5_hash
@@ -71,6 +71,26 @@ def represent(field, value, record):
else:
raise RuntimeError("field representation must take 1 or 2 args")
class CacheRepresenter(object):
def __init__(self):
self.cache = {}
def __call__(self, field, value, row):
cache = self.cache
if field not in cache:
cache[field] = {}
try:
nvalue = cache[field][value]
except KeyError:
try:
nvalue = field.represent(value, row)
except KeyError:
try:
nvalue = field.represent(value, row[field.tablename])
except KeyError:
nvalue = None
if isinstance(field, _repr_ref):
cache[field][value] = nvalue
return nvalue
def safe_int(x):
try:
@@ -626,13 +646,12 @@ class AutocompleteWidget(object):
def __init__(self, request, field, id_field=None, db=None,
orderby=None, limitby=(0, 10), distinct=False,
keyword='_autocomplete_%(tablename)s_%(fieldname)s',
min_length=2, help_fields=None, help_string=None):
min_length=2, help_fields=None, help_string=None, at_beginning = True):
self.help_fields = help_fields or []
self.help_string = help_string
if self.help_fields and not self.help_string:
self.help_string = ' '.join('%%(%s)s' % f.name
for f in self.help_fields)
self.help_string = ' '.join('%%(%s)s' % f.name for f in self.help_fields)
self.request = request
self.keyword = keyword % dict(tablename=field.tablename,
@@ -642,6 +661,7 @@ class AutocompleteWidget(object):
self.limitby = limitby
self.distinct = distinct
self.min_length = min_length
self.at_beginning = at_beginning
self.fields = [field]
if id_field:
self.is_reference = True
@@ -659,8 +679,10 @@ class AutocompleteWidget(object):
field = self.fields[0]
if settings and settings.global_settings.web2py_runtime_gae:
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
else:
elif self.at_beginning:
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
else:
rows = self.db(field.contains(self.request.vars[self.keyword], case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
if rows:
if self.is_reference:
id_field = self.fields[1]
@@ -714,7 +736,7 @@ class AutocompleteWidget(object):
name=name, div_id=div_id, u='F' + self.keyword)
if self.min_length == 0:
attr['_onfocus'] = attr['_onkeyup']
return CAT(INPUT(**attr),
return CAT(INPUT(**attr),
INPUT(_type='hidden', _id=key3, _value=value,
_name=name, requires=field.requires),
DIV(_id=div_id, _style='position:absolute;'))
@@ -727,7 +749,7 @@ class AutocompleteWidget(object):
key=self.keyword, id=attr['_id'], div_id=div_id, u='F' + self.keyword)
if self.min_length == 0:
attr['_onfocus'] = attr['_onkeyup']
return CAT(INPUT(**attr),
return CAT(INPUT(**attr),
DIV(_id=div_id, _style='position:absolute;'))
@@ -818,7 +840,7 @@ def formstyle_bootstrap(form, fields):
controls.add_class('span4')
if isinstance(label, LABEL):
label['_class'] = 'control-label'
label['_class'] = add_class(label.get('_class'),'control-label')
if _submit:
# submit button has unwrapped label and controls, different class
@@ -859,16 +881,16 @@ def formstyle_bootstrap3_stacked(form, fields):
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
controls.add_class('form-control')
elif isinstance(controls, SPAN):
_controls = P(controls.components)
elif isinstance(controls, UL):
for e in controls.elements("input"):
e.add_class('form-control')
if isinstance(label, LABEL):
label['_class'] = 'control-label'
label['_class'] = add_class(label.get('_class'),'control-label')
parent.append(DIV(label, _controls, _class='form-group', _id=id))
return parent
@@ -909,15 +931,17 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
controls.add_class('form-control')
elif isinstance(controls, SPAN):
_controls = P(controls.components,
_controls = P(controls.components,
_class="form-control-static %s" % col_class)
elif isinstance(controls, UL):
for e in controls.elements("input"):
e.add_class('form-control')
elif controls is None or isinstance(controls, basestring):
_controls = P(controls, _class="form-control-static %s" % col_class)
if isinstance(label, LABEL):
label['_class'] = 'control-label %s' % label_col_class
label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class)
parent.append(DIV(label, _controls, _class='form-group', _id=id))
return parent
@@ -1100,10 +1124,12 @@ class SQLFORM(FORM):
raise HTTP(404, "Object not found")
self.record = record
self.record_id = record_id
if keyed:
self.record_id = dict([(k, record and str(record[k]) or None)
for k in table._primarykey])
else:
self.record_id = record_id
self.field_parent = {}
xfields = []
self.fields = fields
@@ -1126,7 +1152,8 @@ class SQLFORM(FORM):
extra_fields = extra_fields or []
self.extra_fields = {}
for extra_field in extra_fields:
self.fields.append(extra_field.name)
if not extra_field.name in self.fields:
self.fields.append(extra_field.name)
self.extra_fields[extra_field.name] = extra_field
extra_field.db = table._db
extra_field.table = table
@@ -1160,6 +1187,14 @@ class SQLFORM(FORM):
label = LABEL(label, label and sep, _for=field_id,
_id=field_id + SQLFORM.ID_LABEL_SUFFIX)
cond = readonly or \
(not ignore_rw and not field.writable and field.readable)
if cond:
label['_class'] = 'readonly'
else:
label['_class'] = ''
row_id = field_id + SQLFORM.ID_ROW_SUFFIX
if field.type == 'id':
self.custom.dspval.id = nbsp
@@ -1188,8 +1223,6 @@ class SQLFORM(FORM):
default = field.default
if isinstance(default, CALLABLETYPES):
default = default()
cond = readonly or \
(not ignore_rw and not field.writable and field.readable)
if default is not None and not cond:
default = field.formatter(default)
@@ -1477,7 +1510,7 @@ class SQLFORM(FORM):
self.custom.end = CAT(self.hidden_fields(), self.custom.end)
auch = record_id and self.errors and self.deleted
auch = self.record_id and self.errors and self.deleted
if self.record_changed and self.detect_record_change:
message_onchange = \
@@ -1520,9 +1553,10 @@ class SQLFORM(FORM):
self.accepted = ret
return ret
if record_id and str(record_id) != str(self.record_id):
raise SyntaxError('user is tampering with form\'s record_id: '
'%s != %s' % (record_id, self.record_id))
if self.record_id:
if str(record_id) != str(self.record_id):
raise SyntaxError('user is tampering with form\'s record_id: '
'%s != %s' % (record_id, self.record_id))
if record_id and dbio and not keyed:
self.vars.id = self.record[self.id_field_name]
@@ -1678,7 +1712,7 @@ class SQLFORM(FORM):
self.vars.update(pk)
else:
ret = False
else:
elif self.table._db._uri:
if record_id:
self.vars.id = self.record[self.id_field_name]
if fields:
@@ -1691,6 +1725,7 @@ class SQLFORM(FORM):
AUTOTYPES = {
type(''): ('string', None),
type(u''): ('string',None),
type(True): ('boolean', None),
type(1): ('integer', IS_INT_IN_RANGE(-1e12, +1e12)),
type(1.0): ('double', IS_FLOAT_IN_RANGE()),
@@ -1755,10 +1790,16 @@ class SQLFORM(FORM):
keywords = keywords[0]
request.vars.keywords = keywords
key = keywords.strip()
if key and ' ' not in key and not '"' in key and not "'" in key:
if key and not '"' in key:
SEARCHABLE_TYPES = ('string', 'text', 'list:string')
parts = [field.contains(
key) for field in fields if field.type in SEARCHABLE_TYPES]
sfields = [field for field in fields if field.type in SEARCHABLE_TYPES]
if settings.global_settings.web2py_runtime_gae:
return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields])
else:
return reduce(lambda a,b:a&b,[
reduce(lambda a,b: a|b, [
field.contains(k) for field in sfields]
) for k in key.split()])
# from https://groups.google.com/forum/#!topic/web2py/hKe6lI25Bv4
# needs testing...
@@ -1772,10 +1813,6 @@ class SQLFORM(FORM):
# filters.append(reduce(lambda a, b: (a & b), all_words_filters))
#parts = filters
else:
parts = None
if parts:
return reduce(lambda a, b: a | b, parts)
else:
return smart_query(fields, key)
@@ -1838,15 +1875,19 @@ class SQLFORM(FORM):
operators = SELECT(*[OPTION(T(option), _value=option) for option in options], _class='form-control')
_id = "%s_%s" % (value_id, name)
if field_type in ['boolean', 'double', 'time', 'integer']:
value_input = SQLFORM.widgets[field_type].widget(field, field.default, _id=_id, _class='form-control')
widget_ = SQLFORM.widgets[field_type]
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
elif field_type == 'date':
iso_format = {'_data-w2p_date_format' : '%Y-%m-%d'}
value_input = SQLFORM.widgets.date.widget(field, field.default, _id=_id, _class='form-control', **iso_format)
iso_format = {'_data-w2p_date_format': '%Y-%m-%d'}
widget_ = SQLFORM.widgets.date
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif field_type == 'datetime':
iso_format = {'_data-w2p_datetime_format' : '%Y-%m-%d %H:%M:%S'}
value_input = SQLFORM.widgets.datetime.widget(field, field.default, _id=_id, _class='form-control', **iso_format)
iso_format = {'_data-w2p_datetime_format': '%Y-%m-%d %H:%M:%S'}
widget_ = SQLFORM.widgets.datetime
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control', **iso_format)
elif (field_type.startswith('reference ') or
field_type.startswith('list:reference ')) and \
hasattr(field.requires, 'options') or \
hasattr(field.requires, 'options'):
value_input = SELECT(
*[OPTION(v, _value=k)
@@ -1856,7 +1897,8 @@ class SQLFORM(FORM):
elif field_type.startswith('reference ') or \
field_type.startswith('list:integer') or \
field_type.startswith('list:reference '):
value_input = SQLFORM.widgets.integer.widget(field, field.default, _id=_id, _class='form-control')
widget_ = SQLFORM.widgets.integer
value_input = widget_.widget(field, field.default, _id=_id, _class=widget_._class + ' form-control')
else:
value_input = INPUT(
_type='text', _id=_id,
@@ -2040,7 +2082,7 @@ class SQLFORM(FORM):
## if it's not an integer
if cache_count is None or isinstance(cache_count, tuple):
if groupby:
c = 'count(*)'
c = 'count(*) AS count_all'
nrows = db.executesql(
'select count(*) from (%s) _tmp;' %
dbset._select(c, left=left, cacheable=True,
@@ -2075,7 +2117,7 @@ class SQLFORM(FORM):
elif isinstance(orderby, Field) and orderby is not field_id:
# here we're with an ASC order on a field stored as orderby
orderby = orderby | field_id
elif (isinstance(orderby, Expression) and
elif (isinstance(orderby, Expression) and
orderby.first and orderby.first is not field_id):
# here we're with a DESC order on a field stored as orderby.first
orderby = orderby | field_id
@@ -2106,10 +2148,8 @@ class SQLFORM(FORM):
# - url has valid signature (vars are not signed, only path_info)
# = url does not contain 'create','delete','edit' (readonly)
if user_signature:
if not (
'/'.join(str(a) for a in args) == '/'.join(request.args) or
URL.verify(request, user_signature=user_signature,
hash_vars=False) or
if not ('/'.join(map(str,args)) == '/'.join(map(str,request.args)) or
URL.verify(request, user_signature=user_signature, hash_vars=False) or
(request.args(len(args)) == 'view' and not logged)):
session.flash = T('not authorized')
redirect(referrer)
@@ -2662,7 +2702,7 @@ class SQLFORM(FORM):
htmltable = TABLE(COLGROUP(*cols), THEAD(head))
tbody = TBODY()
numrec = 0
repr_cache = {}
repr_cache = CacheRepresenter()
for row in rows:
trcols = []
id = row[field_id]
@@ -2675,31 +2715,20 @@ class SQLFORM(FORM):
continue
if field.type == 'blob':
continue
value = row[str(field)]
if isinstance(field, Field.Virtual) and field.tablename in row:
value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
else:
value = row[str(field)]
maxlength = maxtextlengths.get(str(field), maxtextlength)
if field.represent:
if field.type.startswith('reference'):
if field not in repr_cache:
repr_cache[field] = {}
try:
nvalue = repr_cache[field][value]
except KeyError:
try:
nvalue = field.represent(value, row)
except KeyError:
try:
nvalue = field.represent(
value, row[field.tablename])
except KeyError:
nvalue = None
repr_cache[field][value] = nvalue
nvalue = repr_cache(field, value, row)
else:
try:
nvalue = field.represent(value, row)
except KeyError:
try:
nvalue = field.represent(
value, row[field.tablename])
nvalue = field.represent(value, row[field.tablename])
except KeyError:
nvalue = None
value = nvalue

View File

@@ -898,6 +898,9 @@ def render(content="hello world",
if not 'NOESCAPE' in context:
context['NOESCAPE'] = NOESCAPE
if isinstance(content, unicode):
content = content.encode('utf8')
# save current response class
if context and 'response' in context:
old_response_body = context['response'].body

View File

@@ -26,6 +26,7 @@ exclude_lines =
ignore_errors = True
omit = gluon/contrib/*
gluon/tests/*
gluon/packages/*
[html]
directory = coverage_html_report

View File

@@ -4,15 +4,16 @@
Unit tests for gluon.dal
"""
import sys
import os
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.dal import DAL, Field
def tearDownModule():
try:
os.unlink('dummy.db')
@@ -50,6 +51,73 @@ class TestDefaultValidators(unittest.TestCase):
pass
"""
def _prepare_exec_for_file(filename):
module = []
if filename.endswith('.py'):
filename = filename[:-3]
elif os.path.split(filename)[1] == '__init__.py':
filename = os.path.dirname(filename)
else:
raise 'The file provided (%s) does is not a valid Python file.'
filename = os.path.realpath(filename)
dirpath = filename
while 1:
dirpath, extra = os.path.split(dirpath)
module.append(extra)
if not os.path.isfile(os.path.join(dirpath, '__init__.py')):
break
sys.path.insert(0, dirpath)
return '.'.join(module[::-1])
def load_pydal_tests_module():
path = os.path.dirname(os.path.abspath(__file__))
if not os.path.isfile(os.path.join(path, 'web2py.py')):
i = 0
while i < 10:
i += 1
if os.path.exists(os.path.join(path, 'web2py.py')):
break
path = os.path.abspath(os.path.join(path, '..'))
pydal_test_path = os.path.join(
path, "gluon", "packages", "dal", "tests", "__init__.py")
mname = _prepare_exec_for_file(pydal_test_path)
mod = __import__(mname)
return mod
def pydal_suite():
mod = load_pydal_tests_module()
suite = unittest.TestSuite()
tlist = [
getattr(mod, el) for el in mod.__dict__.keys() if el.startswith("Test")
]
for t in tlist:
suite.addTest(unittest.makeSuite(t))
return suite
class TestDALAdapters(unittest.TestCase):
def _run_tests(self):
suite = pydal_suite()
return unittest.TextTestRunner(verbosity=2).run(suite)
def test_mysql(self):
if os.environ.get('APPVEYOR'):
return
os.environ["DB"] = "mysql://root:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
def test_pg8000(self):
if os.environ.get('APPVEYOR'):
return
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
result = self._run_tests()
self.assertTrue(result)
if __name__ == '__main__':
unittest.main()
tearDownModule()

View File

@@ -6,17 +6,43 @@
"""
import re
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon.globals import Response
from gluon.globals import Request, Response, Session
from gluon import URL
def setup_clean_session():
request = Request(env={})
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.folder = 'applications/admin'
response = Response()
session = Session()
session.connect(request, response)
from gluon.globals import current
current.request = request
current.response = response
current.session = session
return current
class testResponse(unittest.TestCase):
#port from python 2.7, needed for 2.5 and 2.6 tests
def assertRegexpMatches(self, text, expected_regexp, msg=None):
"""Fail the test unless the text matches the regular expression."""
if isinstance(expected_regexp, basestring):
expected_regexp = re.compile(expected_regexp)
if not expected_regexp.search(text):
msg = msg or "Regexp didn't match"
msg = '%s: %r not found in %r' % (
msg, expected_regexp.pattern, text)
raise self.failureException(msg)
def test_include_files(self):
def return_includes(response, extensions=None):
@@ -120,5 +146,43 @@ class testResponse(unittest.TestCase):
content = return_includes(response)
self.assertEqual(content, '')
def test_cookies(self):
current = setup_clean_session()
cookie = str(current.response.cookies)
session_key='%s=%s'%(current.response.session_id_name,current.response.session_id)
self.assertRegexpMatches(cookie, r'^Set-Cookie: ')
self.assertTrue(session_key in cookie)
self.assertTrue('Path=/' in cookie)
def test_cookies_secure(self):
current = setup_clean_session()
current.session._fixup_before_save()
cookie = str(current.response.cookies)
self.assertTrue('secure' not in cookie)
current = setup_clean_session()
current.session.secure()
current.session._fixup_before_save()
cookie = str(current.response.cookies)
self.assertTrue('secure' in cookie)
def test_cookies_httponly(self):
current = setup_clean_session()
current.session._fixup_before_save()
cookie = str(current.response.cookies)
self.assertTrue('httponly' in cookie)
current = setup_clean_session()
current.session.httponly_cookies = True
current.session._fixup_before_save()
cookie = str(current.response.cookies)
self.assertTrue('httponly' in cookie)
current = setup_clean_session()
current.session.httponly_cookies = False
current.session._fixup_before_save()
cookie = str(current.response.cookies)
self.assertTrue('httponly' not in cookie)
if __name__ == '__main__':
unittest.main()

View File

@@ -309,7 +309,7 @@ class TestBareHelpers(unittest.TestCase):
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
XML('<h1>HelloWorld</h1>'))
#bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
def testTAG(self):

View File

@@ -146,7 +146,7 @@ class TestList(unittest.TestCase):
'something')
# except if default is especified
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
def test_listgetitem(self):
'''Mantains list behaviour.'''
a = List((1, 2, 3))

View File

@@ -24,42 +24,42 @@ class TestUtils(unittest.TestCase):
data = md5_hash("web2py rocks")
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
def test_compare(self):
""" Tests the compare funciton """
a, b = 'test123', 'test123'
compare_result_true = compare(a, b)
self.assertTrue(compare_result_true)
a, b = 'test123', 'test456'
compare_result_false = compare(a, b)
self.assertFalse(compare_result_false)
def test_simple_hash(self):
""" Tests the simple_hash function """
# no key, no salt, md5
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg='md5')
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
# no key, no salt, sha1
data_sha1 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha1')
self.assertEqual(data_sha1, '00489a46753d8db260c71542611cdef80652c4b7')
# no key, no salt, sha224
data_sha224 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha224')
self.assertEqual(data_sha224, '84d7054271842c2c17983baa2b1447e0289d101140a8c002d49d60da')
# no key, no salt, sha256
data_sha256 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha256')
self.assertEqual(data_sha256, '0849f224d8deb267e4598702aaec1bd749e6caec90832469891012a4be24af08')
# no key, no salt, sha384
data_sha384 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha384')
self.assertEqual(data_sha384,
self.assertEqual(data_sha384,
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
# no key, no salt, sha512
data_sha512 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha512')
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'

File diff suppressed because it is too large Load Diff

View File

@@ -200,8 +200,11 @@ class IS_MATCH(Validator):
self.is_unicode = is_unicode
def __call__(self, value):
if self.is_unicode and not isinstance(value, unicode):
match = self.regex.search(str(value).decode('utf8'))
if self.is_unicode:
if isinstance(value,unicode):
match = self.regex.search(value)
else:
match = self.regex.search(str(value).decode('utf8'))
else:
match = self.regex.search(str(value))
if match is not None:
@@ -691,7 +694,7 @@ class IS_NOT_IN_DB(Validator):
return (value, translate(self.error_message))
else:
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
if row and str(row.id) != str(id):
if row and str(row[table._id]) != str(id):
return (value, translate(self.error_message))
return (value, None)
@@ -2162,29 +2165,22 @@ class IS_DATE(Validator):
INPUT(_type='text', _name='name', requires=IS_DATE())
date has to be in the ISO8960 format YYYY-MM-DD
timezome must be None or a pytz.timezone("America/Chicago") object
"""
def __init__(self, format='%Y-%m-%d',
error_message='Enter date as %(format)s',
timezone=None):
error_message='Enter date as %(format)s'):
self.format = translate(format)
self.error_message = str(error_message)
self.timezone = timezone
self.extremes = {}
def __call__(self, value):
ovalue = value
if isinstance(value, datetime.date):
if self.timezone is not None:
value = value - datetime.timedelta(seconds=self.timezone*3600)
return (value, None)
try:
(y, m, d, hh, mm, ss, t0, t1, t2) = \
time.strptime(value, str(self.format))
value = datetime.date(y, m, d)
if self.timezone is not None:
value = self.timezone.localize(value).astimezone(utc)
return (value, None)
except:
self.extremes.update(IS_DATETIME.nice(self.format))
@@ -2200,11 +2196,7 @@ class IS_DATE(Validator):
format = format.replace('%Y', y)
if year < 1900:
year = 2000
if self.timezone is not None:
d = datetime.datetime(year, value.month, value.day)
d = d.replace(tzinfo=utc).astimezone(self.timezone)
else:
d = datetime.date(year, value.month, value.day)
d = datetime.date(year, value.month, value.day)
return d.strftime(format)
@@ -2255,7 +2247,8 @@ class IS_DATETIME(Validator):
time.strptime(value, str(self.format))
value = datetime.datetime(y, m, d, hh, mm, ss)
if self.timezone is not None:
value = self.timezone.localize(value).astimezone(utc)
# TODO: https://github.com/web2py/web2py/issues/1094 (temporary solution)
value = self.timezone.localize(value).astimezone(utc).replace(tzinfo=None)
return (value, None)
except:
self.extremes.update(IS_DATETIME.nice(self.format))
@@ -2304,8 +2297,7 @@ class IS_DATE_IN_RANGE(IS_DATE):
minimum=None,
maximum=None,
format='%Y-%m-%d',
error_message=None,
timezone=None):
error_message=None):
self.minimum = minimum
self.maximum = maximum
if error_message is None:
@@ -2317,8 +2309,7 @@ class IS_DATE_IN_RANGE(IS_DATE):
error_message = "Enter date in range %(min)s %(max)s"
IS_DATE.__init__(self,
format=format,
error_message=error_message,
timezone=timezone)
error_message=error_message)
self.extremes = dict(min=self.formatter(minimum),
max=self.formatter(maximum))
@@ -2844,9 +2835,11 @@ class CRYPT(object):
self.salt = salt
def __call__(self, value):
value = value and value[:self.max_length]
if len(value) < self.min_length:
v = value and str(value)[:self.max_length]
if not v or len(v) < self.min_length:
return ('', translate(self.error_message))
if isinstance(value, LazyCrypt):
return (value, None)
return (LazyCrypt(self, value), None)
# entropy calculator for IS_STRONG
@@ -3374,7 +3367,8 @@ class IS_IPV4(Validator):
(number == self.localhost)):
ok = False
if not (self.is_private is None or self.is_private ==
(sum([number[0] <= number <= number[1] for number in self.private]) > 0)):
(sum([private_number[0] <= number <= private_number[1]
for private_number in self.private]) > 0)):
ok = False
if not (self.is_automatic is None or self.is_automatic ==
(self.automatic[0] <= number <= self.automatic[1])):
@@ -3479,7 +3473,7 @@ class IS_IPV6(Validator):
from gluon.contrib import ipaddr as ipaddress
try:
ip = ipaddress.IPv6Address(value)
ip = ipaddress.IPv6Address(value.decode('utf-8'))
ok = True
except ipaddress.AddressValueError:
return (value, translate(self.error_message))
@@ -3491,7 +3485,7 @@ class IS_IPV6(Validator):
self.subnets = [self.subnets]
for network in self.subnets:
try:
ipnet = ipaddress.IPv6Network(network)
ipnet = ipaddress.IPv6Network(network.decode('utf-8'))
except (ipaddress.NetmaskValueError, ipaddress.AddressValueError):
return (value, translate('invalid subnet provided'))
if ip in ipnet:
@@ -3700,20 +3694,22 @@ class IS_IPADDRESS(Validator):
def __call__(self, value):
try:
import ipaddress
from ipaddress import ip_address as IPAddress
from ipaddress import IPv6Address, IPv4Address
except ImportError:
from gluon.contrib import ipaddr as ipaddress
from gluon.contrib.ipaddr import (IPAddress, IPv4Address,
IPv6Address)
try:
ip = ipaddress.IPAddress(value)
except ValueError, e:
ip = IPAddress(value.decode('utf-8'))
except ValueError:
return (value, translate(self.error_message))
if self.is_ipv4 and isinstance(ip, ipaddress.IPv6Address):
if self.is_ipv4 and isinstance(ip, IPv6Address):
retval = (value, translate(self.error_message))
elif self.is_ipv6 and isinstance(ip, ipaddress.IPv4Address):
elif self.is_ipv6 and isinstance(ip, IPv4Address):
retval = (value, translate(self.error_message))
elif self.is_ipv4 or isinstance(ip, ipaddress.IPv4Address):
elif self.is_ipv4 or isinstance(ip, IPv4Address):
retval = IS_IPV4(
minip=self.minip,
maxip=self.maxip,
@@ -3723,7 +3719,7 @@ class IS_IPADDRESS(Validator):
is_automatic=self.is_automatic,
error_message=self.error_message
)(value)
elif self.is_ipv6 or isinstance(ip, ipaddress.IPv6Address):
elif self.is_ipv6 or isinstance(ip, IPv6Address):
retval = IS_IPV6(
is_private=self.is_private,
is_link_local=self.is_link_local,

View File

@@ -40,8 +40,8 @@ ProgramInfo = '''%s
%s
%s''' % (ProgramName, ProgramAuthor, ProgramVersion)
if not sys.version[:3] in ['2.5', '2.6', '2.7']:
msg = 'Warning: web2py requires Python 2.5, 2.6 or 2.7 but you are running:\n%s'
if not sys.version[:3] in ['2.6', '2.7']:
msg = 'Warning: web2py requires Python 2.6 or 2.7 but you are running:\n%s'
msg = msg % sys.version
sys.stderr.write(msg)
@@ -56,8 +56,8 @@ def run_system_tests(options):
major_version = sys.version_info[0]
minor_version = sys.version_info[1]
if major_version == 2:
if minor_version in (5, 6):
sys.stderr.write("Python 2.5 or 2.6\n")
if minor_version in (6,):
sys.stderr.write('Python 2.6\n')
ret = subprocess.call(['unit2', '-v', 'gluon.tests'])
elif minor_version in (7,):
call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
@@ -1058,6 +1058,11 @@ def start_schedulers(options):
print 'starting single-scheduler for "%s"...' % app_
run(app_, True, True, None, False, code)
return
# Work around OS X problem: http://bugs.python.org/issue9405
import urllib
urllib.getproxies()
for app in apps:
app_, code = get_code_for_scheduler(app, options)
if not app_:
@@ -1112,12 +1117,12 @@ def start(cron=True):
if hasattr(options, key):
setattr(options, key, getattr(options2, key))
logfile0 = os.path.join('extras', 'examples', 'logging.example.conf')
logfile0 = os.path.join('examples', 'logging.example.conf')
if not os.path.exists('logging.conf') and os.path.exists(logfile0):
import shutil
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
shutil.copyfile('logging.example.conf', logfile0)
sys.stdout.write("OK\n")
shutil.copyfile(logfile0, 'logging.conf')
sys.stdout.write('OK\n')
# ## if -T run doctests (no cron)
if hasattr(options, 'test') and options.test:

View File

@@ -195,7 +195,7 @@ NameVirtualHost *:80
NameVirtualHost *:443
<VirtualHost *:80>
WSGIDaemonProcess web2py user=apache group=apache processes=1 threads=1
WSGIDaemonProcess web2py user=apache group=apache
WSGIProcessGroup web2py
WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py
WSGIPassAuthorization On

View File

@@ -299,7 +299,7 @@ NameVirtualHost *:80
NameVirtualHost *:443
<VirtualHost *:80>
WSGIDaemonProcess web2py user=apache group=apache processes=1 threads=1
WSGIDaemonProcess web2py user=apache group=apache
WSGIProcessGroup web2py
WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py

View File

@@ -1,3 +1,4 @@
#!/bin/bash
echo "This script will:
1) Install modules needed to run web2py on Fedora and CentOS/RHEL
2) Install Python 2.6 to /opt and recompile wsgi if not provided
@@ -27,7 +28,7 @@ Press ENTER to continue...[ctrl+C to abort]"
read CONFIRM
#!/bin/bash
###
### Phase 0 - This may get messy. Lets work from a temporary directory
@@ -301,7 +302,7 @@ NameVirtualHost *:80
NameVirtualHost *:443
<VirtualHost *:80>
WSGIDaemonProcess web2py user=apache group=apache processes=1 threads=1
WSGIDaemonProcess web2py user=apache group=apache
WSGIProcessGroup web2py
WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py
WSGIPassAuthorization On

View File

@@ -0,0 +1,235 @@
#!/bin/bash
# This script will install web2py with nginx+uwsgi on centos 7
# This script is based on excellent tutorial by Justin Ellingwood on
# https://www.digitalocean.com/community/tutorials/how-to-deploy-web2py-python-applications-with-uwsgi-and-nginx-on-centos-7
#
# Phase 1: First, let's ask a few things
#
read -p "Enter username under which web2py will be installed [web2py]: " USERNAME
USERNAME=${USERNAME:-web2py}
read -p "Enter path where web2py will be installed [/opt/web2py_apps]: " WEB2PY_PATH
WEB2PY_PATH=${WEB2PY_PATH:-/opt/web2py_apps}
read -p "Web2py subdirectory will be called: [web2py]: " WEB2PY_APP
WEB2PY_APP=${WEB2PY_APP:-web2py}
read -p "Enter your web2py admin password: " WEB2PY_PASS
read -p "Enter your domain name: " YOUR_SERVER_DOMAIN
# open new user
useradd -d $WEB2PY_PATH $USERNAME
# if it's not already open, let's create a directory for web2py
mkdir -p $WEB2PY_PATH
# now let's create a self signed certificate
cd $WEB2PY_PATH
openssl req -x509 -new -newkey rsa:4096 -days 3652 -nodes -keyout $WEB2PY_APP.key -out $WEB2PY_APP.crt
#
# phase 2: That was all the input that we needed so let's install the components
#
echo "Installing necessary components"
# Verify packages are up to date
yum -y upgrade
# Install required packages
yum install -y epel-release
yum install -y python-devel python-pip gcc nginx wget unzip python-psycopg2 MySQL-python
# download and unzip web2py
echo "Downloading web2py"
cd $WEB2PY_PATH
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
rm web2py_src.zip
# preparing wsgihandler
chown -R $USERNAME.$USERNAME $WEB2PY_PATH/$WEB2PY_APP
mv $WEB2PY_PATH/$WEB2PY_APP/handlers/wsgihandler.py $WEB2PY_PATH/$WEB2PY_APP
# now let's install uwsgi
pip install uwsgi
# preparing directories
mkdir -p /etc/uwsgi/sites
mkdir -p /var/log/uwsgi
mkdir -p /etc/nginx/ssl/
#
# Phase 3: Ok, everything is installed now so we'll configure things
#
# Create configuration file for uwsgi in /etc/uwsgi/$WEB2PY_APP.ini
echo '[uwsgi]
chdir = WEB2PY_PATH_PLACEHOLDER/WEB2PY_APP_PLACEHOLDER
module = wsgihandler:application
master = true
processes = 5
uid = USERNAME_PLACEHOLDER
socket = /run/uwsgi/WEB2PY_APP_PLACEHOLDER.sock
chown-socket = USERNAME_PLACEHOLDER:nginx
chmod-socket = 660
vacuum = true
' >/etc/uwsgi/sites/$WEB2PY_APP.ini
sed -i "s@WEB2PY_PATH_PLACEHOLDER@$WEB2PY_PATH@" /etc/uwsgi/sites/$WEB2PY_APP.ini
sed -i "s@WEB2PY_APP_PLACEHOLDER@$WEB2PY_APP@" /etc/uwsgi/sites/$WEB2PY_APP.ini
sed -i "s@USERNAME_PLACEHOLDER@$USERNAME@" /etc/uwsgi/sites/$WEB2PY_APP.ini
# Create a daemon configuration file for uwsgi
cat > /etc/systemd/system/uwsgi.service <<EOF
[Unit]
Description=uWSGI Emperor service
[Service]
ExecStartPre=/usr/bin/bash -c 'mkdir -p /run/uwsgi; chown USERNAME_PLACEHOLDER:nginx /run/uwsgi'
ExecStart=/usr/bin/uwsgi --emperor /etc/uwsgi/sites
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target
EOF
sed -i "s@USERNAME_PLACEHOLDER@$USERNAME@" /etc/systemd/system/uwsgi.service
#chmod 777 /etc/systemd/system/uwsgi.service
# create a nginx configuration file
cat > /etc/nginx/nginx.conf <<EOF
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" '
'"\$http_user_agent" "\$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name YOUR_SERVER_DOMAIN_PLACEHOLDER;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location ~* /(\w+)/static/ {
root WEB2PY_PATH_PLACEHOLDER/WEB2PY_APP_PLACEHOLDER/applications/;
}
location / {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/WEB2PY_APP_PLACEHOLDER.sock;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
server {
listen 443;
server_name YOUR_SERVER_DOMAIN_PLACEHOLDER;
ssl on;
ssl_certificate /etc/nginx/ssl/WEB2PY_APP_PLACEHOLDER.crt;
ssl_certificate_key /etc/nginx/ssl/WEB2PY_APP_PLACEHOLDER.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
ssl_prefer_server_ciphers on;
location / {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/WEB2PY_APP_PLACEHOLDER.sock;
}
}
}
EOF
sed -i "s@YOUR_SERVER_DOMAIN_PLACEHOLDER@$YOUR_SERVER_DOMAIN@" /etc/nginx/nginx.conf
sed -i "s@WEB2PY_PATH_PLACEHOLDER@$WEB2PY_PATH@" /etc/nginx/nginx.conf
sed -i "s@WEB2PY_APP_PLACEHOLDER@$WEB2PY_APP@" /etc/nginx/nginx.conf
#
# Phase 4: everything is configured now, just a few final touches
#
# copying certificates to nginx directory
mv $WEB2PY_PATH/$WEB2PY_APP.crt* /etc/nginx/ssl
mv $WEB2PY_PATH/$WEB2PY_APP.key* /etc/nginx/ssl
# creating web2py admin password
cd $WEB2PY_PATH/$WEB2PY_APP
python -c "from gluon.main import save_password; save_password('$WEB2PY_PASS',443)"
chown -R $USERNAME.$USERNAME $WEB2PY_PATH/$WEB2PY_APP
# taking care of permissions
chmod 700 /etc/nginx/ssl
usermod -a -G $USERNAME nginx
chmod 710 $WEB2PY_PATH
# enabling daemons
systemctl start nginx
systemctl start uwsgi
systemctl enable nginx
systemctl enable uwsgi
# If firewall is active make sure these ports are open
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --zone=public --add-port=22/tcp --permanent
firewall-cmd --reload
echo
echo 'Web2py is now installed on this server!'
echo

View File

@@ -1,7 +1,8 @@
#!/bin/bash
echo "This script will:
1) install all modules need to run web2py on Ubuntu 14.04
2) install web2py in /home/www-data/
3) create a self signed sll certificate
3) create a self signed ssl certificate
4) setup web2py with mod_wsgi
5) overwrite /etc/apache2/sites-available/default
6) restart apache.
@@ -12,7 +13,7 @@ Press a key to continue...[ctrl+C to abort]"
read CONFIRM
#!/bin/bash
# optional
# dpkg-reconfigure console-setup
# dpkg-reconfigure timezoneconf
@@ -84,13 +85,31 @@ openssl x509 -noout -fingerprint -text < /etc/apache2/ssl/self_signed.cert > /et
echo "rewriting your apache config file to use mod_wsgi"
echo "================================================="
echo '
WSGIDaemonProcess web2py user=www-data group=www-data processes=1 threads=1
WSGIDaemonProcess web2py user=www-data group=www-data
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
WSGIProcessGroup web2py
WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py
WSGIPassAuthorization On
<Directory /home/www-data/web2py>
AllowOverride None
Require all denied
<Files wsgihandler.py>
Require all granted
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
/home/www-data/web2py/applications/$1/static/$2
<Directory /home/www-data/web2py/applications/*/static/>
Options -Indexes
ExpiresActive On
ExpiresDefault "access plus 1 hour"
Require all granted
</Directory>
CustomLog /var/log/apache2/access.log common
ErrorLog /var/log/apache2/error.log

21
scripts/web2py-scheduler.conf Executable file
View File

@@ -0,0 +1,21 @@
description "web2py task scheduler"
# INSTRUCTIONS:
# COPY THIS FILE IN:
# /etc/init/web2py-scheduler.con
#
# To start/stop the scheduler, use
# "sudo start web2py-scheduler"
# "sudo stop web2py-scheduler"
# "sudo status web2py-scheduler"
#
# YOU MAY HAVE TO EDIT PATH TO WEB2PY BELOW
start on (local-filesystems and net-device-up IFACE=eth0)
stop on shutdown
# Give up if restart occurs 8 times in 60 seconds.
respawn limit 8 60
exec sudo -u www-data python /home/www-data/web2py/web2py.py -K parking > /tmp/scheduler.out
respawn

View File

@@ -3,6 +3,9 @@
import os
import sys
import gluon.widget
from multiprocessing import freeze_support
# import gluon.import_all ##### This should be uncommented for py2exe.py
if hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe
@@ -14,17 +17,10 @@ os.chdir(path)
sys.path = [path] + [p for p in sys.path if not p == path]
# import gluon.import_all ##### This should be uncommented for py2exe.py
import gluon.widget
# Start Web2py and Web2py cron service!
if __name__ == '__main__':
try:
from multiprocessing import freeze_support
freeze_support()
except:
sys.stderr.write('Sorry, -K only supported for python 2.6-2.7\n')
if os.environ.has_key("COVERAGE_PROCESS_START"):
freeze_support()
if 'COVERAGE_PROCESS_START' in os.environ:
try:
import coverage
coverage.process_startup()