Compare commits
385 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0409d6f725 | ||
|
|
b6235249da | ||
|
|
fabadcd21f | ||
|
|
8e4ea3497b | ||
|
|
4b0e1856b5 | ||
|
|
94841c90c3 | ||
|
|
f14e5f728c | ||
|
|
8443c17839 | ||
|
|
be8114127e | ||
|
|
4eaef303ff | ||
|
|
319a3fc1dc | ||
|
|
463d643e2c | ||
|
|
0cbed12952 | ||
|
|
b5994e57a4 | ||
|
|
e239b975be | ||
|
|
0259ea3d29 | ||
|
|
db4c008de3 | ||
|
|
7921e5148a | ||
|
|
ee23eab77a | ||
|
|
2344386f77 | ||
|
|
b5e12031c5 | ||
|
|
85e6840cf0 | ||
|
|
4c3006acb4 | ||
|
|
f8f008cab5 | ||
|
|
6bff8af458 | ||
|
|
b67edb083e | ||
|
|
4125a97ce1 | ||
|
|
78cf55bf9a | ||
|
|
931daaff89 | ||
|
|
c6550f0adc | ||
|
|
22c89d8dcc | ||
|
|
1636528a0f | ||
|
|
c02229d79c | ||
|
|
acb05dbfe1 | ||
|
|
fda1117dd7 | ||
|
|
d1fde23182 | ||
|
|
adf4c93860 | ||
|
|
02f1903c3d | ||
|
|
1137027ecc | ||
|
|
229616b9fc | ||
|
|
a75a8cbf46 | ||
|
|
9650ff7516 | ||
|
|
5b90f3f532 | ||
|
|
483092787b | ||
|
|
9b17048882 | ||
|
|
30fe7400f9 | ||
|
|
ada9353a7e | ||
|
|
b0373297e0 | ||
|
|
2dbbef724c | ||
|
|
eb7017fd9a | ||
|
|
4c039574df | ||
|
|
ab900957fe | ||
|
|
f960c8f6df | ||
|
|
d2910327c0 | ||
|
|
dfd6d52192 | ||
|
|
7dd8a3c853 | ||
|
|
71ae754fcd | ||
|
|
0520770a7e | ||
|
|
6b880fb455 | ||
|
|
dd180019a1 | ||
|
|
638f1f902a | ||
|
|
73061e3bf5 | ||
|
|
5a18e29c2e | ||
|
|
721af77c90 | ||
|
|
2cf6797b43 | ||
|
|
5c4145743f | ||
|
|
b4733e4617 | ||
|
|
b51d217d9b | ||
|
|
864dbe73f2 | ||
|
|
b942fc8f7a | ||
|
|
b2a65dbba4 | ||
|
|
d36d4d77f7 | ||
|
|
c8db6d5fb7 | ||
|
|
1b77c2294a | ||
|
|
98a81c9fbd | ||
|
|
4bf5a70dc0 | ||
|
|
d883e3d84e | ||
|
|
db37cf6a58 | ||
|
|
dba5c97d51 | ||
|
|
948bd0c671 | ||
|
|
5d8ff8ba2c | ||
|
|
503cd59adc | ||
|
|
a0bcd2287b | ||
|
|
430163f70b | ||
|
|
52b59e9b71 | ||
|
|
e8f87ea274 | ||
|
|
fb6fa0c448 | ||
|
|
935c95ccfc | ||
|
|
e180e69467 | ||
|
|
5c9d197f93 | ||
|
|
e417d311e5 | ||
|
|
199f93f262 | ||
|
|
64a8880c80 | ||
|
|
257c514bd4 | ||
|
|
12f848c899 | ||
|
|
4de007a946 | ||
|
|
b59a93e24e | ||
|
|
bbed326c20 | ||
|
|
874398c38c | ||
|
|
a9f8fbadae | ||
|
|
e62320ff9f | ||
|
|
b3e606295e | ||
|
|
b8c2bd7303 | ||
|
|
1387b26606 | ||
|
|
c6a7732d32 | ||
|
|
0036d9c45b | ||
|
|
b99fb7dedf | ||
|
|
344590470b | ||
|
|
2c57dc084e | ||
|
|
c17ba0a020 | ||
|
|
7d4b460e1b | ||
|
|
6680ea8ab7 | ||
|
|
353db90a64 | ||
|
|
827e663ac4 | ||
|
|
de399691ce | ||
|
|
46f081c45c | ||
|
|
0fa0dbaeea | ||
|
|
b47511c896 | ||
|
|
e31318eaa8 | ||
|
|
72ee538883 | ||
|
|
b6ddc6098e | ||
|
|
90854eae44 | ||
|
|
2bceb3f95f | ||
|
|
9da1e29014 | ||
|
|
39ba9dc1a9 | ||
|
|
36db9719ef | ||
|
|
125cbd93a0 | ||
|
|
bc267ce17b | ||
|
|
65c87386c1 | ||
|
|
2a245d36f4 | ||
|
|
dcf64a661d | ||
|
|
1c74afc01b | ||
|
|
5dbcda9f38 | ||
|
|
ac02d52f05 | ||
|
|
d4270373e1 | ||
|
|
e4b27080ca | ||
|
|
692791a518 | ||
|
|
9190191c7a | ||
|
|
7bd8f6a1a9 | ||
|
|
64e115f442 | ||
|
|
61f685d225 | ||
|
|
c56fc2f6a0 | ||
|
|
bb2aa29867 | ||
|
|
08b6832809 | ||
|
|
cbbd1246db | ||
|
|
0a79bf3afd | ||
|
|
db5e58e49f | ||
|
|
5030d3144f | ||
|
|
edcc2e44dc | ||
|
|
2cf9f26b0d | ||
|
|
4b99b6fdd7 | ||
|
|
622430583f | ||
|
|
89cc5a5f70 | ||
|
|
6899154fcd | ||
|
|
47c0e461f1 | ||
|
|
93237837ed | ||
|
|
cf20ce5fae | ||
|
|
04c86f07ef | ||
|
|
1a12c4011b | ||
|
|
85bbe15758 | ||
|
|
5816481a44 | ||
|
|
2675e9d229 | ||
|
|
41498917d5 | ||
|
|
8f7acd8154 | ||
|
|
26865421b6 | ||
|
|
b9ee4d4730 | ||
|
|
8fd7a27d5f | ||
|
|
69231bdd7f | ||
|
|
55dfb9e8c4 | ||
|
|
7761219cba | ||
|
|
e31e4e236f | ||
|
|
a43d822412 | ||
|
|
f94bc250eb | ||
|
|
5775d2788d | ||
|
|
048f275076 | ||
|
|
8078d4b0f3 | ||
|
|
5ee8c9c930 | ||
|
|
6659bc0793 | ||
|
|
d7caaf04cc | ||
|
|
e95115deb4 | ||
|
|
42c69b6343 | ||
|
|
d2347dec41 | ||
|
|
8420020c21 | ||
|
|
571fc6d919 | ||
|
|
52ec228eeb | ||
|
|
5848d9acaa | ||
|
|
3e8cbd5a0d | ||
|
|
e276cc2fc1 | ||
|
|
39a048db61 | ||
|
|
df4b896334 | ||
|
|
6d58845153 | ||
|
|
ba1f8bf741 | ||
|
|
a378ab3e51 | ||
|
|
2d866647e2 | ||
|
|
81863d69c9 | ||
|
|
ee2879442f | ||
|
|
ad68d2415d | ||
|
|
928de67f8d | ||
|
|
68296f9e65 | ||
|
|
7ac6edae52 | ||
|
|
1fc90fdb6d | ||
|
|
34a9d72cde | ||
|
|
198ce939d0 | ||
|
|
e31a099cb3 | ||
|
|
cc7e10d216 | ||
|
|
d8b68036c2 | ||
|
|
f9cd7e4ef4 | ||
|
|
896b45b838 | ||
|
|
d6146c9c5d | ||
|
|
b3be806244 | ||
|
|
eac12d3a57 | ||
|
|
2fc081bc3c | ||
|
|
032af7c04d | ||
|
|
8e63825def | ||
|
|
5d2e5dded3 | ||
|
|
61e33da844 | ||
|
|
da9dbaa5d6 | ||
|
|
7543c54bdb | ||
|
|
00608e4f04 | ||
|
|
cdbf48f09b | ||
|
|
f39db6331a | ||
|
|
ef433da190 | ||
|
|
d2375b4187 | ||
|
|
26d87967c5 | ||
|
|
044b2331c3 | ||
|
|
c89614ada6 | ||
|
|
f0aba167b4 | ||
|
|
bde9562b78 | ||
|
|
9a1229470a | ||
|
|
f781b9e1f5 | ||
|
|
fa32b7577b | ||
|
|
68526a0c6d | ||
|
|
ad2003c618 | ||
|
|
c1ecf823d8 | ||
|
|
6134f82452 | ||
|
|
fbb5a8b9bb | ||
|
|
df34869d65 | ||
|
|
28e6999e7d | ||
|
|
f4f77b0cb6 | ||
|
|
23ddb6c3c2 | ||
|
|
b636a5d6e9 | ||
|
|
efc392966e | ||
|
|
cffa59a80c | ||
|
|
82a1b9f628 | ||
|
|
94d2f1453d | ||
|
|
a1875ee362 | ||
|
|
5f13dca712 | ||
|
|
f78d423c92 | ||
|
|
f60ae809b6 | ||
|
|
34dd8af101 | ||
|
|
6bf6ebab1b | ||
|
|
29bf50425b | ||
|
|
8a7612c976 | ||
|
|
97489fd277 | ||
|
|
b86184fe58 | ||
|
|
2ce53e9957 | ||
|
|
d61c372c95 | ||
|
|
73e176365f | ||
|
|
33f12d91a5 | ||
|
|
d0f1286f03 | ||
|
|
04d698109e | ||
|
|
0e9c5caf4d | ||
|
|
509b0a6987 | ||
|
|
e0074ebcac | ||
|
|
918fdf2f0c | ||
|
|
8e827f7a09 | ||
|
|
cf2d5b637b | ||
|
|
236fdcfafc | ||
|
|
ce0f83d00c | ||
|
|
156d771ab3 | ||
|
|
01474c99b0 | ||
|
|
66d15491ca | ||
|
|
376a27da73 | ||
|
|
0f95c13dc7 | ||
|
|
a2e7794b92 | ||
|
|
926de90ee4 | ||
|
|
538f375284 | ||
|
|
4c61c0962d | ||
|
|
9b71646fc5 | ||
|
|
1e66fa3a93 | ||
|
|
57a8dfe034 | ||
|
|
77e7631740 | ||
|
|
ba978d55cf | ||
|
|
12e8ee5c25 | ||
|
|
d293e98b43 | ||
|
|
4f316d0294 | ||
|
|
81e15879d4 | ||
|
|
cd1d6c5af1 | ||
|
|
c7d3758c77 | ||
|
|
040e52278e | ||
|
|
3daf953c66 | ||
|
|
de3d722ac9 | ||
|
|
ff10eab373 | ||
|
|
eb4d159b37 | ||
|
|
5ef7a8e9a1 | ||
|
|
76cfba7047 | ||
|
|
f77f307869 | ||
|
|
5ef8648929 | ||
|
|
ed042685ea | ||
|
|
d09ce57f12 | ||
|
|
169818b275 | ||
|
|
4b14a87463 | ||
|
|
cadf38b4f6 | ||
|
|
a6226d6391 | ||
|
|
5c167907eb | ||
|
|
587ff56a94 | ||
|
|
6f91fdd833 | ||
|
|
6e2f9ad043 | ||
|
|
cdca2793e0 | ||
|
|
a0ee649884 | ||
|
|
380b491724 | ||
|
|
f45bf73992 | ||
|
|
94461724f6 | ||
|
|
c36c391786 | ||
|
|
f6db7c995f | ||
|
|
ccc4b96709 | ||
|
|
71b02e3044 | ||
|
|
99fb1c3010 | ||
|
|
20067d7b93 | ||
|
|
44eb35c617 | ||
|
|
df03317054 | ||
|
|
9d873cbd1c | ||
|
|
1bb4117cbd | ||
|
|
e834186a86 | ||
|
|
1394942feb | ||
|
|
32b9b5c799 | ||
|
|
340d7b5e6f | ||
|
|
302f56ecc1 | ||
|
|
9b12459a82 | ||
|
|
8e3925820c | ||
|
|
279d71d4cd | ||
|
|
258e2e57ae | ||
|
|
9357d810d8 | ||
|
|
54b385b321 | ||
|
|
df039e734c | ||
|
|
58533954dc | ||
|
|
236dc4b943 | ||
|
|
6612fd1cfe | ||
|
|
520950ba74 | ||
|
|
e943aa9c25 | ||
|
|
756aec7206 | ||
|
|
970e2ed35c | ||
|
|
1388c39636 | ||
|
|
6e84737924 | ||
|
|
0ad50630f2 | ||
|
|
f42ee15f5f | ||
|
|
77f154a56b | ||
|
|
2b0bfba649 | ||
|
|
9f1edf267d | ||
|
|
f3bda9ad02 | ||
|
|
f8afc76263 | ||
|
|
65b4aaf842 | ||
|
|
1b729cfbfc | ||
|
|
1aa5f30091 | ||
|
|
b17174c04c | ||
|
|
ac80adc9b4 | ||
|
|
888fa3dfc8 | ||
|
|
f3d815e84b | ||
|
|
9915fdf093 | ||
|
|
ef8f802df9 | ||
|
|
f7bf1020df | ||
|
|
75b8ceb022 | ||
|
|
b4f3784136 | ||
|
|
f1297bb827 | ||
|
|
435ebeaae4 | ||
|
|
537045082c | ||
|
|
4bea52a7b5 | ||
|
|
33295e516f | ||
|
|
f33ccf3366 | ||
|
|
0784680c90 | ||
|
|
e940228eaf | ||
|
|
50769a627a | ||
|
|
e68ecaa131 | ||
|
|
95e6e8577b | ||
|
|
19c83d4ad6 | ||
|
|
9c92bd1050 | ||
|
|
b3b95ccf5f | ||
|
|
cefa30841b | ||
|
|
15ff8669cb | ||
|
|
a921751e8e | ||
|
|
842207ab33 | ||
|
|
c58f29bb9c | ||
|
|
0b0f82b514 | ||
|
|
3ab8a7bfd6 | ||
|
|
c5a9d2c456 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -58,3 +58,5 @@ HOWTO-web2py-devel
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.idea/*
|
||||
site-packages/
|
||||
logs/
|
||||
|
||||
11
.travis.yml
11
.travis.yml
@@ -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"
|
||||
|
||||
37
CHANGELOG
37
CHANGELOG
@@ -1,3 +1,40 @@
|
||||
## 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
|
||||
|
||||
## 2.10.1-2.10.2
|
||||
|
||||
- welcome app defaults to Bootstrap 3
|
||||
|
||||
4
Makefile
4
Makefile
@@ -11,7 +11,7 @@ clean:
|
||||
find ./ -name '*.rej' -exec rm -f {} \;
|
||||
find ./ -name '#*' -exec rm -f {} \;
|
||||
find ./ -name 'Thumbs.db' -exec rm -f {} \;
|
||||
find ./gluon/ -name '.*' -exec rm -f {} \;
|
||||
# find ./gluon/ -name '.*' -exec rm -f {} \;
|
||||
find ./gluon/ -name '*class' -exec rm -f {} \;
|
||||
find ./applications/admin/ -name '.*' -exec rm -f {} \;
|
||||
find ./applications/examples/ -name '.*' -exec rm -f {} \;
|
||||
@@ -32,7 +32,7 @@ update:
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.10.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
echo 'Version 2.13.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
### clean up baisc apps
|
||||
|
||||
@@ -13,7 +13,7 @@ Learn more at http://web2py.com
|
||||
|
||||
Then edit ./app.yaml and replace "yourappname" with yourappname.
|
||||
|
||||
## Import about this GIT repo
|
||||
## Important reminder about this GIT repo
|
||||
|
||||
An important part of web2py is the Database Abstraction Layer (DAL). In early 2015 this was decoupled into a separate code-base (PyDAL). In terms of git, it is a sub-module of the main repository.
|
||||
|
||||
@@ -38,9 +38,10 @@ PyDAL uses a separate stable release cycle to the rest of web2py. PyDAL releases
|
||||
|
||||
## Tests
|
||||
|
||||
[](https://travis-ci.org/web2py/web2py)
|
||||
[](https://travis-ci.org/web2py/web2py)
|
||||
[](https://ci.appveyor.com/project/web2py/web2py)
|
||||
[](https://codecov.io/github/web2py/web2py)
|
||||
|
||||
[](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
|
||||
...
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.10.3-stable+timestamp.2015.04.02.16.28.49
|
||||
Version 2.13.3-stable+timestamp.2015.12.24.08.08.22
|
||||
|
||||
@@ -180,6 +180,11 @@ class Servers:
|
||||
s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address)
|
||||
s.start()
|
||||
|
||||
@staticmethod
|
||||
def waitress(app, address, **options):
|
||||
from waitress import serve
|
||||
serve(app, host=address[0], port=address[1], _quiet=True)
|
||||
|
||||
|
||||
def mongrel2_handler(application, conn, debug=False):
|
||||
"""
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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))
|
||||
@@ -744,7 +778,7 @@ def edit():
|
||||
viewlist.append(aviewpath + '.html')
|
||||
if len(viewlist):
|
||||
editviewlinks = []
|
||||
for v in viewlist:
|
||||
for v in sorted(viewlist):
|
||||
vf = os.path.split(v)[-1]
|
||||
vargs = "/".join([viewpath.replace(os.sep, "/"), vf])
|
||||
editviewlinks.append(A(vf.split(".")[0],
|
||||
@@ -754,6 +788,7 @@ def edit():
|
||||
if len(request.args) > 2 and request.args[1] == 'controllers':
|
||||
controller = (request.args[2])[:-3]
|
||||
functions = find_exposed_functions(data)
|
||||
functions = functions and sorted(functions) or []
|
||||
else:
|
||||
(controller, functions) = (None, None)
|
||||
|
||||
@@ -866,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]
|
||||
@@ -1067,7 +1098,7 @@ def design():
|
||||
for c in controllers:
|
||||
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
||||
items = find_exposed_functions(data)
|
||||
functions[c] = items
|
||||
functions[c] = items and sorted(items) or []
|
||||
|
||||
# Get all views
|
||||
views = sorted(
|
||||
@@ -1205,7 +1236,7 @@ def plugin():
|
||||
for c in controllers:
|
||||
data = safe_read(apath('%s/controllers/%s' % (app, c), r=request))
|
||||
items = find_exposed_functions(data)
|
||||
functions[c] = items
|
||||
functions[c] = items and sorted(items) or []
|
||||
|
||||
# Get all views
|
||||
views = sorted(
|
||||
@@ -1509,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)
|
||||
|
||||
105
applications/admin/controllers/pythonanywhere.py
Normal file
105
applications/admin/controllers/pythonanywhere.py
Normal 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'})
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
9
applications/admin/static/js/jquery.js
vendored
9
applications/admin/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -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();
|
||||
@@ -490,12 +498,12 @@
|
||||
* and prevent clicking on it */
|
||||
disableElement: function(el) {
|
||||
el.addClass('disabled');
|
||||
var method = el.is('button') ? 'html' : 'val';
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
//method = el.attr('name') ? 'html' : 'val';
|
||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||
/*store enabled state if not already disabled */
|
||||
if(el.data('w2p:enable-with') === undefined) {
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
if(el.data('w2p_enable_with') === undefined) {
|
||||
el.data('w2p_enable_with', el[method]());
|
||||
}
|
||||
/*if you don't want to see "working..." on buttons, replace the following
|
||||
* two lines with this one
|
||||
@@ -515,11 +523,11 @@
|
||||
|
||||
/* restore element to its original state which was disabled by 'disableElement' above*/
|
||||
enableElement: function(el) {
|
||||
var method = el.is('button') ? 'val' : 'html';
|
||||
if(el.data('w2p:enable-with') !== undefined) {
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
if(el.data('w2p_enable_with') !== undefined) {
|
||||
/* set to old enabled state */
|
||||
el[method](el.data('w2p:enable-with'));
|
||||
el.removeData('w2p:enable-with');
|
||||
el[method](el.data('w2p_enable_with'));
|
||||
el.removeData('w2p_enable_with');
|
||||
}
|
||||
el.removeClass('disabled');
|
||||
el.unbind('click.w2pDisable');
|
||||
@@ -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"> × </span>').slideDown();
|
||||
},
|
||||
hide_flash: function() {
|
||||
$('.flash').fadeOut(0).html('');
|
||||
$('.w2p_flash').fadeOut(0).html('');
|
||||
},
|
||||
show_if_handler: function(target) {
|
||||
var triggers = {};
|
||||
@@ -586,12 +594,14 @@
|
||||
if(pre_call != undefined) {
|
||||
eval(pre_call);
|
||||
}
|
||||
if(confirm_message != undefined) {
|
||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
if(confirm_message) {
|
||||
if(confirm_message == 'default')
|
||||
confirm_message = w2p_ajax_confirm_message ||
|
||||
'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(target == undefined) {
|
||||
if(method == 'GET') {
|
||||
@@ -634,7 +644,7 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Caches element value in 'w2p:enable-with' data store
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
@@ -646,8 +656,8 @@
|
||||
if(disable_with == undefined) {
|
||||
element.data('w2p_disable_with', element[method]())
|
||||
}
|
||||
if(element.data('w2p:enable-with') === undefined) {
|
||||
element.data('w2p:enable-with', element[method]());
|
||||
if(element.data('w2p_enable_with') === undefined) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
@@ -655,16 +665,16 @@
|
||||
},
|
||||
|
||||
/* Re-enables disabled form elements:
|
||||
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
|
||||
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
|
||||
- Sets disabled property to false
|
||||
*/
|
||||
enableFormElements: function(form) {
|
||||
form.find(web2py.enableSelector).each(function() {
|
||||
var element = $(this),
|
||||
method = element.is('button') ? 'html' : 'val';
|
||||
if(element.data('w2p:enable-with')) {
|
||||
element[method](element.data('w2p:enable-with'));
|
||||
element.removeData('w2p:enable-with');
|
||||
if(element.data('w2p_enable_with')) {
|
||||
element[method](element.data('w2p_enable_with'));
|
||||
element.removeData('w2p_enable_with');
|
||||
}
|
||||
element.prop('disabled', false);
|
||||
});
|
||||
@@ -690,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);
|
||||
@@ -730,4 +740,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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> </i>
|
||||
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function(){
|
||||
jQuery("[rel=tooltip]").tooltip();
|
||||
jQuery(":input").attr("autocomplete","off");
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
|
||||
176
applications/admin/views/pythonanywhere/deploy.html
Normal file
176
applications/admin/views/pythonanywhere/deploy.html
Normal 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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
@@ -22,5 +24,6 @@
|
||||
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
|
||||
- [[Appliances http://www.web2py.com/appliances popup]]
|
||||
- [[web2py utils http://packages.python.org/web2py_utils/ popup]]
|
||||
- [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]]
|
||||
|
||||
#### [[Sites Powered by web2py http://www.web2py.com/poweredby popup]]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
9
applications/examples/static/js/jquery.js
vendored
9
applications/examples/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -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();
|
||||
@@ -490,12 +498,12 @@
|
||||
* and prevent clicking on it */
|
||||
disableElement: function(el) {
|
||||
el.addClass('disabled');
|
||||
var method = el.is('button') ? 'html' : 'val';
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
//method = el.attr('name') ? 'html' : 'val';
|
||||
var disable_with_message = (typeof w2p_ajax_disable_with_message != 'undefined') ? w2p_ajax_disable_with_message : "Working...";
|
||||
/*store enabled state if not already disabled */
|
||||
if(el.data('w2p:enable-with') === undefined) {
|
||||
el.data('w2p:enable-with', el[method]());
|
||||
if(el.data('w2p_enable_with') === undefined) {
|
||||
el.data('w2p_enable_with', el[method]());
|
||||
}
|
||||
/*if you don't want to see "working..." on buttons, replace the following
|
||||
* two lines with this one
|
||||
@@ -515,11 +523,11 @@
|
||||
|
||||
/* restore element to its original state which was disabled by 'disableElement' above*/
|
||||
enableElement: function(el) {
|
||||
var method = el.is('button') ? 'val' : 'html';
|
||||
if(el.data('w2p:enable-with') !== undefined) {
|
||||
var method = el.is('input') ? 'val' : 'html';
|
||||
if(el.data('w2p_enable_with') !== undefined) {
|
||||
/* set to old enabled state */
|
||||
el[method](el.data('w2p:enable-with'));
|
||||
el.removeData('w2p:enable-with');
|
||||
el[method](el.data('w2p_enable_with'));
|
||||
el.removeData('w2p_enable_with');
|
||||
}
|
||||
el.removeClass('disabled');
|
||||
el.unbind('click.w2pDisable');
|
||||
@@ -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"> × </span>').slideDown();
|
||||
},
|
||||
hide_flash: function() {
|
||||
$('.flash').fadeOut(0).html('');
|
||||
$('.w2p_flash').fadeOut(0).html('');
|
||||
},
|
||||
show_if_handler: function(target) {
|
||||
var triggers = {};
|
||||
@@ -586,12 +594,14 @@
|
||||
if(pre_call != undefined) {
|
||||
eval(pre_call);
|
||||
}
|
||||
if(confirm_message != undefined) {
|
||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
if(confirm_message) {
|
||||
if(confirm_message == 'default')
|
||||
confirm_message = w2p_ajax_confirm_message ||
|
||||
'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(target == undefined) {
|
||||
if(method == 'GET') {
|
||||
@@ -634,7 +644,7 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Caches element value in 'w2p:enable-with' data store
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
@@ -646,8 +656,8 @@
|
||||
if(disable_with == undefined) {
|
||||
element.data('w2p_disable_with', element[method]())
|
||||
}
|
||||
if(element.data('w2p:enable-with') === undefined) {
|
||||
element.data('w2p:enable-with', element[method]());
|
||||
if(element.data('w2p_enable_with') === undefined) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
@@ -655,16 +665,16 @@
|
||||
},
|
||||
|
||||
/* Re-enables disabled form elements:
|
||||
- Replaces element text with cached value from 'w2p:enable-with' data store (created in `disableFormElements`)
|
||||
- Replaces element text with cached value from 'w2p_enable_with' data store (created in `disableFormElements`)
|
||||
- Sets disabled property to false
|
||||
*/
|
||||
enableFormElements: function(form) {
|
||||
form.find(web2py.enableSelector).each(function() {
|
||||
var element = $(this),
|
||||
method = element.is('button') ? 'html' : 'val';
|
||||
if(element.data('w2p:enable-with')) {
|
||||
element[method](element.data('w2p:enable-with'));
|
||||
element.removeData('w2p:enable-with');
|
||||
if(element.data('w2p_enable_with')) {
|
||||
element[method](element.data('w2p_enable_with'));
|
||||
element.removeData('w2p_enable_with');
|
||||
}
|
||||
element.prop('disabled', false);
|
||||
});
|
||||
@@ -690,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);
|
||||
@@ -730,4 +740,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="alltables">
|
||||
<table>
|
||||
<table class="table">
|
||||
{{for db in sorted(databases):}}
|
||||
{{for table in databases[db].tables:}}
|
||||
{{qry='%s.%s.id>0'%(db,table)}}
|
||||
@@ -40,7 +40,7 @@
|
||||
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
|
||||
</th>
|
||||
<td>
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn btn-default")}}
|
||||
</td>
|
||||
</tr>
|
||||
{{pass}}
|
||||
@@ -61,7 +61,7 @@
|
||||
</pre>
|
||||
{{pass}}
|
||||
{{if table:}}
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
|
||||
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn btn-default")}}<br/><br/>
|
||||
<h3>{{=T("Rows in Table")}}</h3><br/>
|
||||
{{else:}}
|
||||
<h3>{{=T("Rows selected")}}</h3><br/>
|
||||
@@ -72,8 +72,8 @@
|
||||
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
|
||||
<br/><br/>
|
||||
<h4>{{=T("%s selected", nrows)}}</h4>
|
||||
{{if start>0:}}{{=A(T('previous %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start-step)),_class="btn")}}{{pass}}
|
||||
{{if stop<nrows:}}{{=A(T('next %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start+step)),_class="btn")}}{{pass}}
|
||||
{{if start>0:}}{{=A(T('previous %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start-step)),_class="btn btn-default")}}{{pass}}
|
||||
{{if stop<nrows:}}{{=A(T('next %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start+step)),_class="btn btn-default")}}{{pass}}
|
||||
{{if rows:}}
|
||||
<div style="overflow:auto; width:80%;">
|
||||
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
|
||||
@@ -82,7 +82,7 @@
|
||||
</div>
|
||||
{{pass}}
|
||||
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
|
||||
<a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn">{{=T("export as csv file")}}</a>
|
||||
<a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn btn-default">{{=T("export as csv file")}}</a>
|
||||
{{=formcsv or ''}}
|
||||
|
||||
{{elif request.function=='insert':}}
|
||||
@@ -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 />
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</center>
|
||||
|
||||
<p style="text-align:left;">
|
||||
The source code version works on all supported platforms, including Linux, but it requires Python 2.5, 2.6, or 2.7.
|
||||
The source code version works on all supported platforms, including Linux, but it requires Python 2.6, or 2.7 (recommended).
|
||||
It runs on Windows and most Unix systems, including <b>Linux</b> and <b>BSD</b>.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -17,22 +17,30 @@
|
||||
<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>
|
||||
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
|
||||
<li><a target="_blank" href="www.corebyte.nl">Corebyte</a> (Netherland)</li>
|
||||
<li><a target="_blank" href="https://loadinfo-net.appspot.com">LoadInfo</a> (Bulgaria)</li>
|
||||
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
|
||||
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
|
||||
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
|
||||
<li><a target="_blank" href="http://10Biosystems.com">10BioSystems</a></li>
|
||||
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ auth.define_tables(username=False, signature=False)
|
||||
|
||||
## configure email
|
||||
mail = auth.settings.mailer
|
||||
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.sender')
|
||||
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server')
|
||||
mail.settings.sender = myconf.take('smtp.sender')
|
||||
mail.settings.login = myconf.take('smtp.login')
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
# Language from default.py or 'en' (if the file is not found) is used as
|
||||
# a default_language
|
||||
#
|
||||
# See <web2py-root-dir>/router.example.py for parameter's detail
|
||||
# See <web2py-root-dir>/examples/routes.parametric.example.py for parameter's detail
|
||||
#-------------------------------------------------------------------------------------
|
||||
# To enable this route file you must do the steps:
|
||||
#
|
||||
# 1. rename <web2py-root-dir>/router.example.py to routes.py
|
||||
# 1. rename <web2py-root-dir>/examples/routes.parametric.example.py to routes.py
|
||||
# 2. rename this APP/routes.example.py to APP/routes.py
|
||||
# (where APP - is your application directory)
|
||||
# 3. restart web2py (or reload routes in web2py admin interfase)
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
}
|
||||
@@ -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
9
applications/welcome/static/js/jquery.js
vendored
9
applications/welcome/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -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> <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);
|
||||
|
||||
@@ -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"> × </span>').slideDown();
|
||||
},
|
||||
hide_flash: function() {
|
||||
$('.flash').fadeOut(0).html('');
|
||||
$('.w2p_flash').fadeOut(0).html('');
|
||||
},
|
||||
show_if_handler: function(target) {
|
||||
var triggers = {};
|
||||
@@ -586,12 +594,14 @@
|
||||
if(pre_call != undefined) {
|
||||
eval(pre_call);
|
||||
}
|
||||
if(confirm_message != undefined) {
|
||||
if(confirm_message == 'default') confirm_message = w2p_ajax_confirm_message || 'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
if(confirm_message) {
|
||||
if(confirm_message == 'default')
|
||||
confirm_message = w2p_ajax_confirm_message ||
|
||||
'Are you sure you want to delete this object?';
|
||||
if(!web2py.confirm(confirm_message)) {
|
||||
web2py.stopEverything(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(target == undefined) {
|
||||
if(method == 'GET') {
|
||||
@@ -690,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);
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
25
appveyor.yml
Normal 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
|
||||
46
examples/web.config
Normal file
46
examples/web.config
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- app configuration for web2py on IIS -->
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="WSGI_HANDLER" value="gluon.main.wsgibase" />
|
||||
<add key="WSGI_RESTART_FILE_REGEX" value=".*((routes\.py)|(\.config))$" />
|
||||
</appSettings>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<clear />
|
||||
<rule name="static" enabled="true" stopProcessing="true">
|
||||
<match url="^(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$" />
|
||||
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
|
||||
<action type="Rewrite" url="applications/{R:1}/static/{R:2}" logRewrittenUrl="false" />
|
||||
</rule>
|
||||
<rule name="web2py_app" enabled="true" stopProcessing="true">
|
||||
<match url="(.*)" ignoreCase="false" />
|
||||
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
|
||||
<action type="Rewrite" url="handler.web2py/{R:1}" appendQueryString="true" />
|
||||
</rule>
|
||||
</rules>
|
||||
<outboundRules>
|
||||
<rule name="static_version_cache_control" preCondition="static_version">
|
||||
<match serverVariable="RESPONSE_Cache-Control" pattern=".*" />
|
||||
<action type="Rewrite" value="max-age=315360000" />
|
||||
<conditions>
|
||||
</conditions>
|
||||
</rule>
|
||||
<rule name="static_version_Expires" preCondition="static_version">
|
||||
<match serverVariable="RESPONSE_Expires" pattern=".*" />
|
||||
<action type="Rewrite" value="Thu, 31 Dec 2037 23:59:59 GMT" />
|
||||
</rule>
|
||||
<preConditions>
|
||||
<preCondition name="static_version">
|
||||
<add input="{REQUEST_URI}" pattern="(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$" />
|
||||
</preCondition>
|
||||
</preConditions>
|
||||
</outboundRules>
|
||||
</rewrite>
|
||||
<handlers>
|
||||
<!-- replace SCRIPT_PROCESSOR with the configured handler for python -->
|
||||
<add name="Python_via_FastCGI" path="handler.web2py" verb="*" modules="FastCgiModule" scriptProcessor="SCRIPT_PROCESSOR" resourceType="Unspecified" requireAccess="Script" />
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
150
fabfile.py
vendored
Normal file
150
fabfile.py
vendored
Normal 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')
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -99,7 +99,7 @@ class CacheAbstract(object):
|
||||
"""
|
||||
|
||||
cache_stats_name = 'web2py_cache_statistics'
|
||||
max_ram_utilization = 90 # percent
|
||||
max_ram_utilization = None # percent
|
||||
|
||||
def __init__(self, request=None):
|
||||
"""Initializes the object
|
||||
@@ -353,7 +353,7 @@ class CacheOnDisk(CacheAbstract):
|
||||
raise KeyError
|
||||
|
||||
self.wait_portalock(val_file)
|
||||
value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
||||
value = pickle.load(val_file)
|
||||
val_file.close()
|
||||
return value
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -473,9 +473,14 @@ class CacheOnDisk(CacheAbstract):
|
||||
if item and ((dt is None) or (item[0] > now - dt)):
|
||||
value = item[1]
|
||||
else:
|
||||
value = f()
|
||||
try:
|
||||
value = f()
|
||||
except:
|
||||
self.storage.release(CacheAbstract.cache_stats_name)
|
||||
self.storage.release(key)
|
||||
raise
|
||||
self.storage[key] = (now, value)
|
||||
self.storage.safe_apply(CacheAbstract.cache_stats_name, inc_misses,
|
||||
self.storage.safe_apply(CacheAbstract.cache_stats_name, inc_misses,
|
||||
default_value={'hit_total': 0, 'misses': 0})
|
||||
|
||||
self.storage.release(CacheAbstract.cache_stats_name)
|
||||
@@ -601,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]
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,27 @@ 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 = dict(((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 +481,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,41 +501,43 @@ 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)
|
||||
raise
|
||||
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))
|
||||
# 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))
|
||||
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)]
|
||||
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
|
||||
@@ -542,8 +545,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
|
||||
@@ -551,16 +553,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
|
||||
@@ -600,6 +598,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,
|
||||
@@ -649,10 +649,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
|
||||
@@ -665,18 +667,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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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('-','')
|
||||
|
||||
@@ -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()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,11 +67,12 @@ def RedisCache(*args, **vars):
|
||||
|
||||
locker.acquire()
|
||||
try:
|
||||
if not hasattr(RedisCache, 'redis_instance'):
|
||||
RedisCache.redis_instance = RedisClient(*args, **vars)
|
||||
instance_name = 'redis_instance_' + current.request.application
|
||||
if not hasattr(RedisCache, instance_name):
|
||||
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
|
||||
return getattr(RedisCache, instance_name)
|
||||
finally:
|
||||
locker.release()
|
||||
return RedisCache.redis_instance
|
||||
|
||||
|
||||
class RedisClient(object):
|
||||
|
||||
@@ -126,7 +126,7 @@ class History:
|
||||
def globals_dict(self):
|
||||
"""Returns a dictionary view of the globals.
|
||||
"""
|
||||
return dict((name, cPickle.loads(val))
|
||||
return dict((name, pickle.loads(val))
|
||||
for name, val in zip(self.global_names, self.globals))
|
||||
|
||||
def add_unpicklable(self, statement, names):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" %
|
||||
|
||||
@@ -4,7 +4,24 @@ from hashlib import sha1
|
||||
|
||||
class Stripe:
|
||||
"""
|
||||
Usage:
|
||||
Use in WEB2PY (guaranteed PCI compliant)
|
||||
|
||||
def pay():
|
||||
from gluon.contrib.stripe import StripeForm
|
||||
form = StripeForm(
|
||||
pk=STRIPE_PUBLISHABLE_KEY,
|
||||
sk=STRIPE_SECRET_KEY,
|
||||
amount=150, # $1.5 (amount is in cents)
|
||||
description="Nothing").process()
|
||||
if form.accepted:
|
||||
payment_id = form.response['id']
|
||||
redirect(URL('thank_you'))
|
||||
elif form.errors:
|
||||
redirect(URL('pay_error'))
|
||||
return dict(form=form)
|
||||
|
||||
Low level API:
|
||||
|
||||
key='<api key>'
|
||||
d = Stripe(key).charge(
|
||||
amount=100, # 1 dollar!!!!
|
||||
@@ -22,22 +39,6 @@ class Stripe:
|
||||
{u'fee': 0, u'description': u'test charge', u'created': 1321242072, u'refunded': False, u'livemode': False, u'object': u'charge', u'currency': u'usd', u'amount': 100, u'paid': True, u'id': u'ch_sdjasgfga83asf', u'card': {u'exp_month': 5, u'country': u'US', u'object': u'card', u'last4': u'4242', u'exp_year': 2012, u'type': u'Visa'}}
|
||||
if paid is True than transaction was processed
|
||||
|
||||
Use in WEB2PY (guaranteed PCI compliant)
|
||||
|
||||
def pay():
|
||||
from gluon.contrib.stripe import StripeForm
|
||||
form = StripeForm(
|
||||
pk=STRIPE_PUBLISHABLE_KEY,
|
||||
sk=STRIPE_SECRET_KEY,
|
||||
amount=150, # $1.5 (amount is in cents)
|
||||
description="Nothing").process()
|
||||
if form.accepted:
|
||||
payment_id = form.response['id']
|
||||
redirect(URL('thank_you'))
|
||||
elif form.errors:
|
||||
redirect(URL('pay_error'))
|
||||
return dict(form=form)
|
||||
|
||||
"""
|
||||
|
||||
URL_CHARGE = 'https://%s:@api.stripe.com/v1/charges'
|
||||
@@ -188,37 +189,36 @@ jQuery(function(){
|
||||
<h3>Payment Amount: {{=currency_symbol}} {{="%.2f" % (0.01*amount)}}</h3>
|
||||
<form action="" method="POST" id="payment-form" class="form-horizontal">
|
||||
|
||||
<div class="form-row control-group">
|
||||
<label class="control-label">Card Number</label>
|
||||
<div class="controls">
|
||||
<div class="form-row form-group">
|
||||
<label class="col-sm-2 control-label">Card Number</label>
|
||||
<div class="controls col-sm-10">
|
||||
<input type="text" size="20" data-stripe="number"
|
||||
placeholder="4242424242424242"/>
|
||||
placeholder="4242424242424242" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row control-group">
|
||||
<label class="control-label">CVC</label>
|
||||
<div class="controls">
|
||||
<div class="form-row form-group">
|
||||
<label class="col-sm-2 control-label">CVC</label>
|
||||
<div class="controls col-sm-10">
|
||||
<input type="text" size="4" style="width:80px" data-stripe="cvc"
|
||||
placeholder="XXX"/>
|
||||
placeholder="XXX" class="form-control"/>
|
||||
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row control-group">
|
||||
<label class="control-label">Expiration</label>
|
||||
<div class="controls">
|
||||
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
|
||||
placeholder="MM"/>
|
||||
<div class="form-row form-group">
|
||||
<label class="col-sm-2 control-label">Expiration</label>
|
||||
<div class="controls col-sm-10">
|
||||
<input type="text" size="2" style="width:40px; display:inline-block"
|
||||
data-stripe="exp-month" placeholder="MM" class="form-control"/>
|
||||
/
|
||||
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
|
||||
placeholder="YYYY"/>
|
||||
<input type="text" size="4" style="width:80px; display:inline-block"
|
||||
data-stripe="exp-year" placeholder="YYYY" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<div class="form-row form-group">
|
||||
<div class="controls col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Submit Payment</button>
|
||||
<div class="payment-errors error hidden"></div>
|
||||
</div>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
10
gluon/dal.py
10
gluon/dal.py
@@ -7,14 +7,14 @@
|
||||
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
Takes care of adapting pyDAL to web2py's needs
|
||||
--------------------------------------------
|
||||
-----------------------------------------------
|
||||
"""
|
||||
|
||||
from pydal import DAL as DAL
|
||||
from pydal import Field
|
||||
from pydal.objects import Row, Rows, Table, Query, Expression
|
||||
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
||||
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||
import copy_reg as copyreg
|
||||
|
||||
|
||||
def _default_validators(db, field):
|
||||
"""
|
||||
@@ -81,12 +81,12 @@ def _default_validators(db, field):
|
||||
requires[0] = validators.IS_EMPTY_OR(requires[0])
|
||||
return requires
|
||||
|
||||
from gluon import serializers as w2p_serializers
|
||||
from gluon.serializers import custom_json, xml
|
||||
from gluon.utils import web2py_uuid
|
||||
from gluon import sqlhtml
|
||||
|
||||
|
||||
DAL.serializers = w2p_serializers
|
||||
DAL.serializers = {'json': custom_json, 'xml': xml}
|
||||
DAL.validators_method = _default_validators
|
||||
DAL.uuid = lambda x: web2py_uuid()
|
||||
DAL.representers = {
|
||||
|
||||
@@ -26,6 +26,8 @@ import gluon.settings as settings
|
||||
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
||||
from gluon.settings import global_settings
|
||||
from gluon import recfile
|
||||
from gluon.cache import CacheInRam
|
||||
from gluon.fileutils import copystream
|
||||
import hashlib
|
||||
import portalocker
|
||||
try:
|
||||
@@ -47,8 +49,7 @@ import cgi
|
||||
import urlparse
|
||||
import copy
|
||||
import tempfile
|
||||
from gluon.cache import CacheInRam
|
||||
from gluon.fileutils import copystream
|
||||
|
||||
|
||||
FMT = '%a, %d-%b-%Y %H:%M:%S PST'
|
||||
PAST = 'Sat, 1-Jan-1971 00:00:00'
|
||||
@@ -82,13 +83,22 @@ less_template = '<link href="%s" rel="stylesheet/less" type="text/css" />'
|
||||
css_inline = '<style type="text/css">\n%s\n</style>'
|
||||
js_inline = '<script type="text/javascript">\n%s\n</script>'
|
||||
|
||||
template_mapping = {
|
||||
'css': css_template,
|
||||
'js': js_template,
|
||||
'coffee': coffee_template,
|
||||
'ts': typescript_template,
|
||||
'less': less_template,
|
||||
'css:inline': css_inline,
|
||||
'js:inline': js_inline
|
||||
}
|
||||
|
||||
# IMPORTANT:
|
||||
# this is required so that pickled dict(s) and class.__dict__
|
||||
# are sorted and web2py can detect without ambiguity when a session changes
|
||||
class SortingPickler(Pickler):
|
||||
def save_dict(self, obj):
|
||||
self.write(EMPTY_DICT if self.bin else MARK+DICT)
|
||||
self.write(EMPTY_DICT if self.bin else MARK + DICT)
|
||||
self.memoize(obj)
|
||||
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
|
||||
|
||||
@@ -193,11 +203,12 @@ class Request(Storage):
|
||||
self.is_https = False
|
||||
self.is_local = False
|
||||
self.global_settings = settings.global_settings
|
||||
self._uuid = None
|
||||
|
||||
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():
|
||||
@@ -275,7 +286,7 @@ class Request(Storage):
|
||||
"""
|
||||
self._vars = copy.copy(self.get_vars)
|
||||
for key, value in self.post_vars.iteritems():
|
||||
if not key in self._vars:
|
||||
if key not in self._vars:
|
||||
self._vars[key] = value
|
||||
else:
|
||||
if not isinstance(self._vars[key], list):
|
||||
@@ -306,13 +317,21 @@ class Request(Storage):
|
||||
self.parse_all_vars()
|
||||
return self._vars
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
"""Lazily uuid
|
||||
"""
|
||||
if self._uuid is None:
|
||||
self.compute_uuid()
|
||||
return self._uuid
|
||||
|
||||
def compute_uuid(self):
|
||||
self.uuid = '%s/%s.%s.%s' % (
|
||||
self._uuid = '%s/%s.%s.%s' % (
|
||||
self.application,
|
||||
self.client.replace(':', '_'),
|
||||
self.now.strftime('%Y-%m-%d.%H-%M-%S'),
|
||||
web2py_uuid())
|
||||
return self.uuid
|
||||
return self._uuid
|
||||
|
||||
def user_agent(self):
|
||||
from gluon.contrib import user_agent_parser
|
||||
@@ -436,29 +455,32 @@ class Response(Storage):
|
||||
return page
|
||||
|
||||
def include_meta(self):
|
||||
s = "\n";
|
||||
s = "\n"
|
||||
for meta in (self.meta or {}).iteritems():
|
||||
k, v = meta
|
||||
if isinstance(v,dict):
|
||||
s = s+'<meta'+''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
|
||||
if isinstance(v, dict):
|
||||
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), xmlescape(v[key])) for key in v) +' />\n'
|
||||
else:
|
||||
s = s+'<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
|
||||
s += '<meta name="%s" content="%s" />\n' % (k, xmlescape(v))
|
||||
self.write(s, escape=False)
|
||||
|
||||
def include_files(self, extensions=None):
|
||||
|
||||
"""
|
||||
Caching method for writing out files.
|
||||
Includes files (usually in the head).
|
||||
Can minify and cache local files
|
||||
By default, caches in ram for 5 minutes. To change,
|
||||
response.cache_includes = (cache_method, time_expire).
|
||||
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
||||
"""
|
||||
from gluon import URL
|
||||
|
||||
files = []
|
||||
ext_files = []
|
||||
has_js = has_css = False
|
||||
for item in self.files:
|
||||
if extensions and not item.split('.')[-1] in extensions:
|
||||
if isinstance(item, (list, tuple)):
|
||||
ext_files.append(item)
|
||||
continue
|
||||
if extensions and not item.rpartition('.')[2] in extensions:
|
||||
continue
|
||||
if item in files:
|
||||
continue
|
||||
@@ -487,10 +509,13 @@ class Response(Storage):
|
||||
time_expire)
|
||||
else:
|
||||
files = call_minify()
|
||||
s = ''
|
||||
|
||||
files.extend(ext_files)
|
||||
s = []
|
||||
for item in files:
|
||||
if isinstance(item, str):
|
||||
f = item.lower().split('?')[0]
|
||||
ext = f.rpartition('.')[2]
|
||||
# if static_version we need also to check for
|
||||
# static_version_urls. In that case, the _.x.x.x
|
||||
# bit would have already been added by the URL()
|
||||
@@ -498,24 +523,15 @@ class Response(Storage):
|
||||
if self.static_version and not self.static_version_urls:
|
||||
item = item.replace(
|
||||
'/static/', '/static/_%s/' % self.static_version, 1)
|
||||
if f.endswith('.css'):
|
||||
s += css_template % item
|
||||
elif f.endswith('.js'):
|
||||
s += js_template % item
|
||||
elif f.endswith('.coffee'):
|
||||
s += coffee_template % item
|
||||
elif f.endswith('.ts'):
|
||||
# http://www.typescriptlang.org/
|
||||
s += typescript_template % item
|
||||
elif f.endswith('.less'):
|
||||
s += less_template % item
|
||||
tmpl = template_mapping.get(ext)
|
||||
if tmpl:
|
||||
s.append(tmpl % item)
|
||||
elif isinstance(item, (list, tuple)):
|
||||
f = item[0]
|
||||
if f == 'css:inline':
|
||||
s += css_inline % item[1]
|
||||
elif f == 'js:inline':
|
||||
s += js_inline % item[1]
|
||||
self.write(s, escape=False)
|
||||
tmpl = template_mapping.get(f)
|
||||
if tmpl:
|
||||
s.append(tmpl % item[1])
|
||||
self.write(''.join(s), escape=False)
|
||||
|
||||
def stream(self,
|
||||
stream,
|
||||
@@ -663,7 +679,7 @@ class Response(Storage):
|
||||
return handler(request, self, methods)
|
||||
|
||||
def toolbar(self):
|
||||
from html import DIV, SCRIPT, BEAUTIFY, TAG, URL, A
|
||||
from gluon.html import DIV, SCRIPT, BEAUTIFY, TAG, A
|
||||
BUTTON = TAG.button
|
||||
admin = URL("admin", "default", "design", extension='html',
|
||||
args=current.request.application)
|
||||
@@ -1007,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
|
||||
@@ -1058,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -370,13 +370,12 @@ 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'
|
||||
)
|
||||
request.compute_uuid() # requires client
|
||||
request.url = environ['PATH_INFO']
|
||||
|
||||
# ##################################################
|
||||
@@ -424,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
|
||||
|
||||
Submodule gluon/packages/dal updated: b08cb1f779...dcfb5f58aa
@@ -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)")
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -41,9 +41,7 @@ def enable_autocomplete_and_history(adir, env):
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
readline.parse_and_bind("bind ^I rl_complete"
|
||||
if sys.platform == 'darwin'
|
||||
else "tab: complete")
|
||||
readline.parse_and_bind("tab: complete")
|
||||
history_file = os.path.join(adir, '.pythonhistory')
|
||||
try:
|
||||
readline.read_history_file(history_file)
|
||||
@@ -131,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)
|
||||
|
||||
196
gluon/sqlhtml.py
196
gluon/sqlhtml.py
@@ -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
|
||||
@@ -58,9 +58,12 @@ def represent(field, value, record):
|
||||
f = field.represent
|
||||
if not callable(f):
|
||||
return str(value)
|
||||
n = f.func_code.co_argcount - len(f.func_defaults or [])
|
||||
if getattr(f, 'im_self', None):
|
||||
n -= 1
|
||||
if hasattr(f,'func_code'):
|
||||
n = f.func_code.co_argcount - len(f.func_defaults or [])
|
||||
if getattr(f, 'im_self', None):
|
||||
n -= 1
|
||||
else:
|
||||
n = 1
|
||||
if n == 1:
|
||||
return f(value)
|
||||
elif n == 2:
|
||||
@@ -68,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:
|
||||
@@ -623,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,
|
||||
@@ -639,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
|
||||
@@ -656,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]
|
||||
@@ -711,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;'))
|
||||
@@ -724,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;'))
|
||||
|
||||
|
||||
@@ -815,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
|
||||
@@ -856,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
|
||||
@@ -906,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
|
||||
@@ -1097,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
|
||||
@@ -1123,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
|
||||
@@ -1157,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
|
||||
@@ -1185,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)
|
||||
@@ -1205,6 +1241,9 @@ class SQLFORM(FORM):
|
||||
elif field.type == 'boolean':
|
||||
inp = self.widgets.boolean.widget(
|
||||
field, default, _disabled=True)
|
||||
elif isinstance(field.type, SQLCustomType) and callable(field.type.represent):
|
||||
# SQLCustomType has a represent, use it
|
||||
inp = field.type.represent(default, record)
|
||||
else:
|
||||
inp = field.formatter(default)
|
||||
if getattr(field, 'show_if', None):
|
||||
@@ -1246,6 +1285,9 @@ class SQLFORM(FORM):
|
||||
dspval = ''
|
||||
elif field.type == 'blob':
|
||||
continue
|
||||
elif isinstance(field.type, SQLCustomType) and callable(field.type.widget):
|
||||
# SQLCustomType has a widget, use it
|
||||
inp = field.type.widget(field, default)
|
||||
else:
|
||||
field_type = widget_class.match(str(field.type)).group()
|
||||
field_type = field_type in self.widgets and field_type or 'string'
|
||||
@@ -1468,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 = \
|
||||
@@ -1511,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]
|
||||
@@ -1669,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:
|
||||
@@ -1682,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()),
|
||||
@@ -1746,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...
|
||||
@@ -1763,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)
|
||||
|
||||
@@ -1829,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)
|
||||
@@ -1847,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,
|
||||
@@ -1958,7 +2009,8 @@ class SQLFORM(FORM):
|
||||
cache_count=None,
|
||||
client_side_delete=False,
|
||||
ignore_common_filters=None,
|
||||
auto_pagination=True):
|
||||
auto_pagination=True,
|
||||
use_cursor=False):
|
||||
|
||||
formstyle = formstyle or current.response.formstyle
|
||||
|
||||
@@ -2030,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,
|
||||
@@ -2060,18 +2112,15 @@ class SQLFORM(FORM):
|
||||
# is unique and usually indexed. See issue #679
|
||||
if not orderby:
|
||||
orderby = field_id
|
||||
else:
|
||||
if isinstance(orderby, Expression):
|
||||
if orderby.first:
|
||||
# here we're with a DESC order on a field
|
||||
# stored as orderby.first
|
||||
if orderby.first is not field_id:
|
||||
orderby = orderby | field_id
|
||||
else:
|
||||
# here we're with an ASC order on a field
|
||||
# stored as orderby
|
||||
if orderby is not field_id:
|
||||
orderby = orderby | field_id
|
||||
elif isinstance(orderby, list):
|
||||
orderby = reduce(lambda a,b: a|b, orderby)
|
||||
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
|
||||
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
|
||||
return orderby
|
||||
|
||||
def url(**b):
|
||||
@@ -2099,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)
|
||||
@@ -2533,7 +2580,7 @@ class SQLFORM(FORM):
|
||||
|
||||
cursor = True
|
||||
# figure out what page we are one to setup the limitby
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
cursor = request.vars.cursor or True
|
||||
limitby = (0, paginate)
|
||||
try:
|
||||
@@ -2555,7 +2602,7 @@ class SQLFORM(FORM):
|
||||
table_fields = [field for field in fields
|
||||
if (field.tablename in tablenames and
|
||||
not(isinstance(field, Field.Virtual)))]
|
||||
if dbset._db._adapter.dbengine == 'google:datastore':
|
||||
if dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
groupby=groupby, limitby=limitby,
|
||||
reusecursor=cursor,
|
||||
@@ -2565,6 +2612,7 @@ class SQLFORM(FORM):
|
||||
rows = dbset.select(left=left, orderby=orderby,
|
||||
groupby=groupby, limitby=limitby,
|
||||
cacheable=True, *table_fields)
|
||||
next_cursor = None
|
||||
except SyntaxError:
|
||||
rows = None
|
||||
next_cursor = None
|
||||
@@ -2583,7 +2631,7 @@ class SQLFORM(FORM):
|
||||
console.append(DIV(message or '', _class='web2py_counter'))
|
||||
|
||||
paginator = UL()
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore':
|
||||
if paginate and dbset._db._adapter.dbengine == 'google:datastore' and use_cursor:
|
||||
# this means we may have a large table with an unknown number of rows.
|
||||
try:
|
||||
page = int(request.vars.page or 1) - 1
|
||||
@@ -2654,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]
|
||||
@@ -2667,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
|
||||
@@ -2708,6 +2745,9 @@ class SQLFORM(FORM):
|
||||
_href='%s/%s' % (upload, value))
|
||||
else:
|
||||
value = ''
|
||||
elif isinstance(field.type, SQLCustomType) and callable(field.type.represent):
|
||||
# SQLCustomType has a represent, use it
|
||||
value = field.type.represent(value, row)
|
||||
if isinstance(value, str):
|
||||
value = truncate_string(value, maxlength)
|
||||
elif not isinstance(value, XmlComponent):
|
||||
|
||||
@@ -269,31 +269,33 @@ class FastStorage(dict):
|
||||
|
||||
|
||||
class List(list):
|
||||
|
||||
"""
|
||||
Like a regular python list but a[i] if i is out of bounds returns None
|
||||
instead of `IndexOutOfBounds`
|
||||
Like a regular python list but callable.
|
||||
When a(i) is called if i is out of bounds returns None
|
||||
instead of `IndexError`.
|
||||
"""
|
||||
|
||||
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
|
||||
"""Allows to use a special syntax for fast-check of `request.args()`
|
||||
validity
|
||||
|
||||
Args:
|
||||
"""Allows to use a special syntax for fast-check of
|
||||
`request.args()` validity.
|
||||
:params:
|
||||
i: index
|
||||
default: use this value if arg not found
|
||||
cast: type cast
|
||||
otherwise: can be:
|
||||
|
||||
- None: results in a 404
|
||||
- str: redirect to this address
|
||||
- callable: calls the function (nothing is passed)
|
||||
|
||||
otherwise:
|
||||
will be executed when:
|
||||
- casts fail
|
||||
- value not found, dont have default and otherwise is
|
||||
especified
|
||||
can be:
|
||||
- None: results in a 404
|
||||
- str: redirect to this address
|
||||
- callable: calls the function (nothing is passed)
|
||||
Example:
|
||||
You can use::
|
||||
|
||||
request.args(0,default=0,cast=int,otherwise='http://error_url')
|
||||
request.args(0,default=0,cast=int,otherwise=lambda:...)
|
||||
|
||||
"""
|
||||
n = len(self)
|
||||
if 0 <= i < n or -n <= i < 0:
|
||||
@@ -301,23 +303,24 @@ class List(list):
|
||||
elif default is DEFAULT:
|
||||
value = None
|
||||
else:
|
||||
value, cast = default, False
|
||||
if cast:
|
||||
try:
|
||||
value, cast, otherwise = default, False, False
|
||||
try:
|
||||
if cast:
|
||||
value = cast(value)
|
||||
except (ValueError, TypeError):
|
||||
from http import HTTP, redirect
|
||||
if otherwise is None:
|
||||
raise HTTP(404)
|
||||
elif isinstance(otherwise, str):
|
||||
redirect(otherwise)
|
||||
elif callable(otherwise):
|
||||
return otherwise()
|
||||
else:
|
||||
raise RuntimeError("invalid otherwise")
|
||||
if not value and otherwise:
|
||||
raise ValueError('Otherwise will raised.')
|
||||
except (ValueError, TypeError):
|
||||
from http import HTTP, redirect
|
||||
if otherwise is None:
|
||||
raise HTTP(404)
|
||||
elif isinstance(otherwise, str):
|
||||
redirect(otherwise)
|
||||
elif callable(otherwise):
|
||||
return otherwise()
|
||||
else:
|
||||
raise RuntimeError("invalid otherwise")
|
||||
return value
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,6 +4,7 @@ from test_http import *
|
||||
from test_cache import *
|
||||
from test_contenttype import *
|
||||
from test_fileutils import *
|
||||
from test_globals import *
|
||||
from test_html import *
|
||||
from test_is_url import *
|
||||
from test_languages import *
|
||||
|
||||
@@ -26,6 +26,7 @@ exclude_lines =
|
||||
ignore_errors = True
|
||||
omit = gluon/contrib/*
|
||||
gluon/tests/*
|
||||
gluon/packages/*
|
||||
|
||||
[html]
|
||||
directory = coverage_html_report
|
||||
|
||||
@@ -13,6 +13,7 @@ fix_sys_path(__file__)
|
||||
|
||||
from storage import Storage
|
||||
from cache import CacheInRam, CacheOnDisk, Cache
|
||||
from gluon.dal import DAL, Field
|
||||
|
||||
oldcwd = None
|
||||
|
||||
@@ -30,6 +31,11 @@ def tearDownModule():
|
||||
if oldcwd:
|
||||
os.chdir(oldcwd)
|
||||
oldcwd = None
|
||||
try:
|
||||
os.unlink('dummy.db')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class TestCache(unittest.TestCase):
|
||||
@@ -107,7 +113,34 @@ class TestCache(unittest.TestCase):
|
||||
cache.clear(regex=r'a*')
|
||||
self.assertEqual(cache('a1', lambda: 2, 0), 2)
|
||||
self.assertEqual(cache('a2', lambda: 3, 100), 3)
|
||||
return
|
||||
|
||||
def testDALcache(self):
|
||||
s = Storage({'application': 'admin',
|
||||
'folder': 'applications/admin'})
|
||||
cache = Cache(s)
|
||||
db = DAL(check_reserved=['all'])
|
||||
db.define_table('t_a', Field('f_a'))
|
||||
db.t_a.insert(f_a='test')
|
||||
db.commit()
|
||||
a = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
|
||||
b = db(db.t_a.id > 0).select(cache=(cache.ram, 60), cacheable=True)
|
||||
self.assertEqual(a.as_csv(), b.as_csv())
|
||||
c = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
|
||||
d = db(db.t_a.id > 0).select(cache=(cache.disk, 60), cacheable=True)
|
||||
self.assertEqual(c.as_csv(), d.as_csv())
|
||||
self.assertEqual(a.as_csv(), c.as_csv())
|
||||
self.assertEqual(b.as_csv(), d.as_csv())
|
||||
e = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
|
||||
f = db(db.t_a.id > 0).select(cache=(cache.disk, 60))
|
||||
self.assertEqual(e.as_csv(), f.as_csv())
|
||||
self.assertEqual(a.as_csv(), f.as_csv())
|
||||
g = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
|
||||
h = db(db.t_a.id > 0).select(cache=(cache.ram, 60))
|
||||
self.assertEqual(g.as_csv(), h.as_csv())
|
||||
self.assertEqual(a.as_csv(), h.as_csv())
|
||||
db.t_a.drop()
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setUpModule() # pre-python-2.7
|
||||
|
||||
@@ -4,36 +4,120 @@
|
||||
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')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class TestDALSubclass(unittest.TestCase):
|
||||
|
||||
def testRun(self):
|
||||
import gluon.serializers as mserializers
|
||||
from gluon.serializers import custom_json, xml
|
||||
from gluon import sqlhtml
|
||||
db = DAL(check_reserved=['all'])
|
||||
self.assertEqual(db.serializers, mserializers)
|
||||
self.assertEqual(db.serializers['json'], custom_json)
|
||||
self.assertEqual(db.serializers['xml'], xml)
|
||||
self.assertEqual(db.representers['rows_render'], sqlhtml.represent)
|
||||
self.assertEqual(db.representers['rows_xml'], sqlhtml.SQLTABLE)
|
||||
db.close()
|
||||
|
||||
def testSerialization(self):
|
||||
import pickle
|
||||
db = DAL(check_reserved=['all'])
|
||||
db.define_table('t_a', Field('f_a'))
|
||||
db.t_a.insert(f_a='test')
|
||||
a = db(db.t_a.id>0).select(cacheable=True)
|
||||
a = db(db.t_a.id > 0).select(cacheable=True)
|
||||
s = pickle.dumps(a)
|
||||
b = pickle.loads(s)
|
||||
self.assertEqual(a.db, b.db)
|
||||
db.t_a.drop()
|
||||
db.close()
|
||||
|
||||
""" TODO:
|
||||
class TestDefaultValidators(unittest.TestCase):
|
||||
def testRun(self):
|
||||
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()
|
||||
|
||||
188
gluon/tests/test_globals.py
Normal file
188
gluon/tests/test_globals.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Unit tests for gluon.globals
|
||||
"""
|
||||
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from fix_path import fix_sys_path
|
||||
|
||||
fix_sys_path(__file__)
|
||||
|
||||
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):
|
||||
response.include_files(extensions)
|
||||
return response.body.getvalue()
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.js'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="/a/static/css/file.js" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.coffee'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="/a/static/css/file.coffee" type="text/coffee"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="/a/static/css/file.ts" type="text/typescript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.less'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<link href="/a/static/css/file.less" rel="stylesheet/less" type="text/css" />')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('css:inline', 'background-color; white;'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<style type="text/css">\nbackground-color; white;\n</style>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('js:inline', 'alert("hello")'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script type="text/javascript">\nalert("hello")\n</script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js')
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content,
|
||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||
response.files.append('https://code.jquery.com/jquery-1.11.3.min.js?var=0')
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content,
|
||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
|
||||
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
|
||||
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
|
||||
)
|
||||
|
||||
|
||||
response = Response()
|
||||
response.files.append(URL('a', 'static', 'css/file.js'))
|
||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||
content = return_includes(response, extensions=['css'])
|
||||
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||
|
||||
#regr test for #628
|
||||
response = Response()
|
||||
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '')
|
||||
|
||||
#regr test for #628
|
||||
response = Response()
|
||||
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(['js', 'http://maps.google.com/maps/api/js?sensor=false'])
|
||||
content = return_includes(response)
|
||||
self.assertEqual(content, '<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>')
|
||||
|
||||
response = Response()
|
||||
response.files.append(('js1', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||
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()
|
||||
@@ -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):
|
||||
|
||||
@@ -120,6 +120,7 @@ class TestStorageList(unittest.TestCase):
|
||||
|
||||
|
||||
class TestList(unittest.TestCase):
|
||||
|
||||
""" Tests Storage.List (fast-check for request.args()) """
|
||||
|
||||
def test_listcall(self):
|
||||
@@ -134,6 +135,23 @@ class TestList(unittest.TestCase):
|
||||
self.assertEqual(a(3, cast=int), 1234)
|
||||
a.append('x')
|
||||
self.assertRaises(HTTP, a, 4, cast=int)
|
||||
b = List()
|
||||
# default is always returned when especified
|
||||
self.assertEqual(b(0, cast=int, default=None), None)
|
||||
self.assertEqual(b(0, cast=int, default=None, otherwise='teste'), None)
|
||||
self.assertEqual(b(0, cast=int, default='a', otherwise='teste'), 'a')
|
||||
# if don't have value and otherwise is especified it will called
|
||||
self.assertEqual(b(0, otherwise=lambda: 'something'), 'something')
|
||||
self.assertEqual(b(0, cast=int, otherwise=lambda: 'something'),
|
||||
'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))
|
||||
self.assertEqual(a[0], 1)
|
||||
self.assertEqual(a[::-1], [3, 2, 1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -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'
|
||||
|
||||
1354
gluon/tools.py
1354
gluon/tools.py
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
@@ -611,7 +614,7 @@ class IS_IN_DB(Validator):
|
||||
|
||||
def count(values, s=self.dbset, f=field):
|
||||
return s(f.belongs(map(int, values))).count()
|
||||
if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
|
||||
if GoogleDatastoreAdapter is not None and isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
|
||||
range_ids = range(0, len(values), 30)
|
||||
total = sum(count(values[i:i + 30]) for i in range_ids)
|
||||
if total == len(values):
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
235
scripts/setup-web2py-nginx-uwsgi-centos7.sh
Normal file
235
scripts/setup-web2py-nginx-uwsgi-centos7.sh
Normal 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
|
||||
|
||||
@@ -84,7 +84,7 @@ server {
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
|
||||
ssl_protocols SSLv3 TLSv1;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
keepalive_timeout 70;
|
||||
location / {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
|
||||
@@ -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
|
||||
|
||||
169
scripts/setup-web2py-ws2012r2.ps1
Normal file
169
scripts/setup-web2py-ws2012r2.ps1
Normal file
@@ -0,0 +1,169 @@
|
||||
"This script will work fine for a few cases 'by default':"
|
||||
" - completely CLEAN WS2012R2 host"
|
||||
" - python 2.7 installed in the default path"
|
||||
" - wfasctgi installed on the default path"
|
||||
"It'll install web2py under the default website "
|
||||
" You can use it as a boilerplate to automate your deployments"
|
||||
" but it still is released AS IT IS. "
|
||||
"BIG FAT WARNING: It will install a bunch of dependecies
|
||||
Inspect the source before executing it"
|
||||
""
|
||||
""
|
||||
$ErrorActionPreference = 'stop'
|
||||
|
||||
$REALLY_SURE = Read-Host "Do you want to start with web2py deployment? [y/N]"
|
||||
if (!@('y', 'Y') -contains $REALLY_SURE) {
|
||||
"Ok, Exiting without doing anything"
|
||||
exit 1
|
||||
}
|
||||
#setting root folder
|
||||
$rootfolder = $pwd
|
||||
|
||||
### utilities - start
|
||||
function ask_a_question($question) {
|
||||
$response = Read-Host "$question [Y/n]"
|
||||
if (@('Y', 'y', '', $null) -contains $response) {
|
||||
return $true
|
||||
} else {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function unzip_me {
|
||||
#Load the assembly
|
||||
[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
|
||||
#Unzip the file
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($pathToZip, $targetDir)
|
||||
}
|
||||
|
||||
|
||||
### utilities - end
|
||||
|
||||
#install 4.5 that is needed for a bunch of things anyway
|
||||
Install-WindowsFeature Net-Framework-45-Core
|
||||
|
||||
#fetch web2py
|
||||
$web2py_url = 'http://www.web2py.com/examples/static/web2py_src.zip'
|
||||
$web2py_file = "$pwd\web2py_src.zip"
|
||||
if (!(Test-Path $web2py_file)) {
|
||||
(new-object net.webclient).DownloadFile($web2py_url, $web2py_file)
|
||||
}
|
||||
#Load the assembly
|
||||
[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
|
||||
#Unzip the file
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($web2py_file, $pwd)
|
||||
|
||||
#features installation (IIS, needed modules, python, chocolatey, etc)
|
||||
$installfeatures = ask_a_question('Do you want to install needed features?')
|
||||
|
||||
if ($installfeatures) {
|
||||
Install-WindowsFeature Web-Server,Web-Default-Doc,Web-Static-Content,Web-Http-Redirect,Web-Http-Logging,Web-Request-Monitor,`
|
||||
Web-Http-Tracing,Web-Stat-Compression,Web-Dyn-Compression,Web-Filtering,Web-Basic-Auth,Web-Windows-Auth,Web-AppInit,`
|
||||
Web-CGI,Web-WebSockets,Web-Mgmt-Console,Web-Net-Ext45
|
||||
}
|
||||
|
||||
$copy_web2py = ask_a_question("Copy web2py to the default website root?")
|
||||
if ($copy_web2py) {
|
||||
Import-Module WebAdministration
|
||||
$available_websites = Get-Website
|
||||
if ($available_websites[0] -eq $null) {
|
||||
$default_one = $available_websites
|
||||
} else {
|
||||
$default_one = $available_websites[0]
|
||||
}
|
||||
$iis_root = [System.Environment]::ExpandEnvironmentVariables($default_one.PhysicalPath)
|
||||
Copy-Item "$rootfolder\web2py\*" $iis_root -Recurse
|
||||
$rootfolder = $iis_root
|
||||
$acl = (Get-Item $rootfolder).GetAccessControl('Access')
|
||||
$identity = "BUILTIN\IIS_IUSRS"
|
||||
$fileSystemRights = "Modify"
|
||||
$inheritanceFlags = "ContainerInherit, ObjectInherit"
|
||||
$propagationFlags = "None"
|
||||
$accessControlType = "Allow"
|
||||
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $inheritanceFlags, $propagationFlags, $accessControlType)
|
||||
$acl.SetAccessRule($rule)
|
||||
Set-Acl $rootfolder $acl
|
||||
}
|
||||
|
||||
$create_cert = ask_a_question("Do you want to create a self-signed SSL cert?")
|
||||
if ($create_cert) {
|
||||
$cert = New-SelfSignedCertificate -DnsName ("localtest.me","*.localtest.me") -CertStoreLocation cert:\LocalMachine\My
|
||||
$rootStore = Get-Item cert:\LocalMachine\Root
|
||||
$rootStore.Open("ReadWrite")
|
||||
$rootStore.Add($cert)
|
||||
$rootStore.Close();
|
||||
Import-Module WebAdministration
|
||||
Set-Location IIS:\SslBindings
|
||||
New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https
|
||||
$cert | New-Item 0.0.0.0!443
|
||||
Set-Location $pwd
|
||||
}
|
||||
|
||||
"checking for chocolatey"
|
||||
if (Get-Command "choco.exe" -ErrorAction SilentlyContinue)
|
||||
{
|
||||
"chocolatey found"
|
||||
} else {
|
||||
"installing chocolatey"
|
||||
(new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1') | iex
|
||||
}
|
||||
"installing url-rewrite"
|
||||
choco install UrlRewrite
|
||||
$pythonexe = Read-Host 'Python.exe path [C:\Python27\python.exe]'
|
||||
if (($pythonexe -eq '') -or ($pythonexe -eq $null)) {
|
||||
$pythonexe = 'C:\Python27\python.exe'
|
||||
}
|
||||
if (!(Test-Path $pythonexe)) {
|
||||
"ERROR: python executable not found"
|
||||
$pythonwanted = ask_a_question("do you want to install it automatically?")
|
||||
|
||||
if ($pythonwanted) {
|
||||
choco install webpicmd
|
||||
WebpiCmd.exe /Install /Products:WFastCgi_21_279
|
||||
$pythonexe = 'C:\Python27\python.exe'
|
||||
}
|
||||
else {
|
||||
exit 1
|
||||
}
|
||||
|
||||
}
|
||||
$wfastcgipath = Read-Host 'wfastcgi.py path [C:\Python27\Scripts\wfastcgi.py]'
|
||||
if (($wfastcgipath -eq '') -or ($wfastcgipath -eq $null)) {
|
||||
$wfastcgipath = 'C:\Python27\Scripts\wfastcgi.py'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $wfastcgipath)) {
|
||||
"ERROR: wfastcgi.py not found"
|
||||
|
||||
$wfastcgiwanted = ask_a_question("do you want to install it automatically?")
|
||||
if ($wfastcgiwanted) {
|
||||
choco install webpicmd
|
||||
WebpiCmd.exe /Install /Products:WFastCgi_21_279
|
||||
} else {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
$pythondir = Split-Path c:\python27\python.exe
|
||||
#installing dependencies
|
||||
$env:Path = $env:Path + ";$pythondir;$pythondir\Scripts"
|
||||
|
||||
pip install pypiwin32
|
||||
|
||||
$PW = Read-Host 'Web2py Admin Password'
|
||||
|
||||
$appcmdpath = "$env:windir\system32\inetsrv\appcmd.exe"
|
||||
|
||||
& $appcmdpath set config /section:system.webServer/fastCGI "/+[fullPath='$pythonexe', arguments='$wfastcgipath']"
|
||||
& $appcmdpath unlock config -section:system.webServer/handlers
|
||||
|
||||
& cd $rootfolder
|
||||
& $pythonexe -c "from gluon.main import save_password; save_password('$PW',443)"
|
||||
|
||||
$webconfig_template = Join-Path $rootfolder "examples\web.config"
|
||||
$destination = Join-Path $rootfolder "web.config"
|
||||
$scriptprocessor = 'scriptProcessor="{0}|{1}"' -f $pythonexe, $wfastcgipath
|
||||
|
||||
(Get-Content $webconfig_template) | Foreach-Object {$_ -replace 'scriptProcessor="SCRIPT_PROCESSOR"', $scriptprocessor} | where {$_ -ne ""} | Set-Content $destination
|
||||
""
|
||||
"Installation finished. Web2py is available either on http://localhost/ or at https://localtest.me/"
|
||||
""
|
||||
78
scripts/tickets2slack.py
Executable file
78
scripts/tickets2slack.py
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Post error tickets to slack on a 5 minute schedule.
|
||||
#
|
||||
# Proper use depends on having created a web-hook through Slack, and having set
|
||||
# that value in your app's model as the value of global_settings.slack_hook.
|
||||
# Details on creating web-hooks can be found at https://slack.com/integrations
|
||||
#
|
||||
# requires the Requests module for posting to slack, other requirements are
|
||||
# standard or provided by web2py
|
||||
#
|
||||
# Usage (on Unices), replace myapp with the name of your application and run:
|
||||
# nohup python web2py.py -S myapp -M -R scripts/tickets2slack.py &
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import pickle
|
||||
import json
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError as e:
|
||||
print "missing module 'Requests', aborting."
|
||||
sys.exit(1)
|
||||
|
||||
from gluon import URL
|
||||
from gluon.utils import md5_hash
|
||||
from gluon.restricted import RestrictedError
|
||||
from gluon.settings import global_settings
|
||||
|
||||
|
||||
path = os.path.join(request.folder, 'errors')
|
||||
sent_errors_file = os.path.join(path, 'slack_errors.pickle')
|
||||
hashes = {}
|
||||
if os.path.exists(sent_errors_file):
|
||||
try:
|
||||
with open(sent_errors_file, 'rb') as f:
|
||||
hashes = pickle.load(f)
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
# ## CONFIGURE HERE
|
||||
SLEEP_MINUTES = 5
|
||||
ALLOW_DUPLICATES = False
|
||||
global_settings.slack_hook = global_settings.slack_hook or \
|
||||
'https://hooks.slack.com/services/your_service'
|
||||
# ## END CONFIGURATION
|
||||
|
||||
while 1:
|
||||
for file_name in os.listdir(path):
|
||||
if file_name == 'slack_errors.pickle':
|
||||
continue
|
||||
|
||||
if not ALLOW_DUPLICATES:
|
||||
key = md5_hash(file_name)
|
||||
if key in hashes:
|
||||
continue
|
||||
hashes[key] = 1
|
||||
|
||||
error = RestrictedError()
|
||||
|
||||
try:
|
||||
error.load(request, request.application, file_name)
|
||||
except Exception as _:
|
||||
continue # not an exception file?
|
||||
|
||||
url = URL(a='admin', f='ticket', args=[request.application, file],
|
||||
scheme=True)
|
||||
payload = json.dumps(dict(text="Error in %(app)s.\n%(url)s" %
|
||||
dict(app=request.application, url=url)))
|
||||
|
||||
requests.post(global_settings.slack_hook, data=dict(payload=payload))
|
||||
|
||||
with open(sent_errors_file, 'wb') as f:
|
||||
pickle.dump(hashes, f)
|
||||
time.sleep(SLEEP_MINUTES * 60)
|
||||
21
scripts/web2py-scheduler.conf
Executable file
21
scripts/web2py-scheduler.conf
Executable 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
|
||||
@@ -62,7 +62,7 @@ do_start()
|
||||
start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \
|
||||
&& return 1
|
||||
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE \
|
||||
start-stop-daemon --start --quiet -m --pidfile $PIDFILE \
|
||||
${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \
|
||||
--background --exec $DAEMON -- $DAEMON_ARGS \
|
||||
|| return 2
|
||||
|
||||
13
web2py.py
13
web2py.py
@@ -3,6 +3,8 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
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 +16,14 @@ 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
|
||||
# important that this import is after the os.chdir
|
||||
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user