Compare commits
259
Commits
R-2.15.0b2
...
mdipierro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da2f027501 | ||
|
|
7877220057 | ||
|
|
0c465b27a4 | ||
|
|
b26184b010 | ||
|
|
d83d3535be | ||
|
|
82a4d0b600 | ||
|
|
a037537497 | ||
|
|
fa99620240 | ||
|
|
ee19a48521 | ||
|
|
698ca8bbb8 | ||
|
|
9c6df11459 | ||
|
|
ee4eb3f15a | ||
|
|
a0cdad2ddf | ||
|
|
cc8abd0a0f | ||
|
|
f44d617445 | ||
|
|
c17d6bb5d1 | ||
|
|
c9999499bb | ||
|
|
61b5343f66 | ||
|
|
164a271ca8 | ||
|
|
419dc7b5fc | ||
|
|
fa31a6b61b | ||
|
|
7aafd05cbb | ||
|
|
79ba8cb6e4 | ||
|
|
c3355b7457 | ||
|
|
546440ece0 | ||
|
|
44e27bf65f | ||
|
|
82cf2eb44d | ||
|
|
31514060d7 | ||
|
|
1d489d0fde | ||
|
|
f161cc4f3b | ||
|
|
9e02a768cd | ||
|
|
fce88037bd | ||
|
|
009d5ce48c | ||
|
|
521d5bce97 | ||
|
|
d1efc8b55d | ||
|
|
ad3c69155b | ||
|
|
421aec162a | ||
|
|
072311fd2c | ||
|
|
5dcbae0b37 | ||
|
|
76f3384aae | ||
|
|
f9606fabde | ||
|
|
623f3b9947 | ||
|
|
f4203ae4d6 | ||
|
|
089359f3ea | ||
|
|
8c9cfff578 | ||
|
|
699513b961 | ||
|
|
b1a9b67c54 | ||
|
|
8fe4d860a1 | ||
|
|
b7b16da08f | ||
|
|
6994cee6c9 | ||
|
|
9bf8ca9c3b | ||
|
|
35a1f6f342 | ||
|
|
f048e45494 | ||
|
|
cfdda257ed | ||
|
|
dca4639b85 | ||
|
|
4ecdc2bc73 | ||
|
|
c7d91108bb | ||
|
|
4697e0e45b | ||
|
|
4f51647b2f | ||
|
|
79b877f6b4 | ||
|
|
553b7cf51a | ||
|
|
20411ce45f | ||
|
|
b11d6f6498 | ||
|
|
44c3c3b0e4 | ||
|
|
e9dcac4ecd | ||
|
|
4a2a02d1fe | ||
|
|
622b29366a | ||
|
|
130e37d708 | ||
|
|
160ec9b044 | ||
|
|
ee8c401020 | ||
|
|
c8a7943e8f | ||
|
|
7bbf6b93f3 | ||
|
|
3410810c1c | ||
|
|
26081782c8 | ||
|
|
b732ab6d53 | ||
|
|
071622b9d9 | ||
|
|
fd0f169a55 | ||
|
|
c9fd0fd71e | ||
|
|
d59e7f35ed | ||
|
|
999e7b7512 | ||
|
|
7035398681 | ||
|
|
2c2c9d3aa2 | ||
|
|
65b3e6dda9 | ||
|
|
d424e5d317 | ||
|
|
a1417df67c | ||
|
|
551c19bcaf | ||
|
|
8aa07760d2 | ||
|
|
8bd8db5edc | ||
|
|
7c1fb6643e | ||
|
|
e44a12eaf8 | ||
|
|
12253ab757 | ||
|
|
30b3d84f24 | ||
|
|
166e268308 | ||
|
|
14e58276cf | ||
|
|
eb7222aa92 | ||
|
|
009a0b87f2 | ||
|
|
aeb0244b2c | ||
|
|
57522ddeb1 | ||
|
|
934042fc04 | ||
|
|
4bfa9c1686 | ||
|
|
925f928843 | ||
|
|
2f5eb409b6 | ||
|
|
228d3c41b6 | ||
|
|
3ab91b9fe9 | ||
|
|
5db3b21437 | ||
|
|
2aa8fd22a2 | ||
|
|
d5eb8d8f17 | ||
|
|
c4e08eeeb3 | ||
|
|
c1a3d5d67e | ||
|
|
b2841de6f3 | ||
|
|
c7d415fefd | ||
|
|
2e572aee9a | ||
|
|
dd14d1c8c9 | ||
|
|
afa5fac074 | ||
|
|
a5b76f1dec | ||
|
|
1e586b32a4 | ||
|
|
dfcffe1a9d | ||
|
|
2e6f529dfd | ||
|
|
0ae3ee506f | ||
|
|
6b103f7e3a | ||
|
|
02c32ebace | ||
|
|
603cc7092a | ||
|
|
8590aae2e8 | ||
|
|
511245a68c | ||
|
|
4a347a4f78 | ||
|
|
5f4c47729b | ||
|
|
5975e4767f | ||
|
|
1800304ade | ||
|
|
9af0d8c3c9 | ||
|
|
2e2d3482e4 | ||
|
|
48cc63741e | ||
|
|
a605e43fa6 | ||
|
|
8eda21ca86 | ||
|
|
e8cf50326d | ||
|
|
27c9250efb | ||
|
|
3ecdd1c11b | ||
|
|
912c22d593 | ||
|
|
4b38186b51 | ||
|
|
83f9016528 | ||
|
|
a6044068cd | ||
|
|
4aefb93ab4 | ||
|
|
2861dc4215 | ||
|
|
087280ec17 | ||
|
|
d06a1f9dc6 | ||
|
|
c06fc67064 | ||
|
|
bd167aa94a | ||
|
|
c9a71a7055 | ||
|
|
02e50cadbc | ||
|
|
9f69ab9753 | ||
|
|
56d10a40c6 | ||
|
|
76b09393e4 | ||
|
|
e880da0d0e | ||
|
|
9694c66703 | ||
|
|
f22e3a7624 | ||
|
|
bd7ee209ea | ||
|
|
5e6c3dba81 | ||
|
|
135f41041d | ||
|
|
1d0f322d09 | ||
|
|
892fba9e54 | ||
|
|
6e5c8b62cc | ||
|
|
852a9e0127 | ||
|
|
485f868cd1 | ||
|
|
de8b2a477b | ||
|
|
8533fa0d00 | ||
|
|
7e96ecafd7 | ||
|
|
86ea728f4f | ||
|
|
a29947f298 | ||
|
|
dc29f33365 | ||
|
|
834460f0cc | ||
|
|
5353e17c66 | ||
|
|
d2022ca500 | ||
|
|
c560f607af | ||
|
|
3eea1f68f4 | ||
|
|
10b6b93cb2 | ||
|
|
0b41ed36f9 | ||
|
|
90e20a4f39 | ||
|
|
a744835f21 | ||
|
|
ebc614bf91 | ||
|
|
b7270df9c3 | ||
|
|
4a47bb8e83 | ||
|
|
213c4ee7d1 | ||
|
|
7088b74d42 | ||
|
|
752b5a8cdb | ||
|
|
56e6d682d6 | ||
|
|
c1881fa205 | ||
|
|
cc2cae5de4 | ||
|
|
cedd74c1ad | ||
|
|
d5167f2ed6 | ||
|
|
1014d3e86e | ||
|
|
f3bba29287 | ||
|
|
cd2ec98a26 | ||
|
|
0e613f2d7f | ||
|
|
561828fa60 | ||
|
|
19efbfecfa | ||
|
|
d43604c3ff | ||
|
|
ec21f72ce3 | ||
|
|
aa0313c59b | ||
|
|
0a013e3edc | ||
|
|
fb4c114d85 | ||
|
|
b47e1334d5 | ||
|
|
ce0b255747 | ||
|
|
05d2ced779 | ||
|
|
d144ff7d65 | ||
|
|
780510dc32 | ||
|
|
7a5f611e76 | ||
|
|
b7b8a009f2 | ||
|
|
113df51ef9 | ||
|
|
4e704ca6f7 | ||
|
|
047ed786ac | ||
|
|
ca1e5156ba | ||
|
|
4854b84ff9 | ||
|
|
aa252cdbd8 | ||
|
|
e3ec4d4075 | ||
|
|
81b000d47a | ||
|
|
305dac4976 | ||
|
|
876cc634a8 | ||
|
|
c004d2c16e | ||
|
|
e3cce4d752 | ||
|
|
2c84b88466 | ||
|
|
fe652c851b | ||
|
|
c183c7b18b | ||
|
|
e2c9875cd5 | ||
|
|
579c54f926 | ||
|
|
b2cb0bc189 | ||
|
|
3f6e8c755a | ||
|
|
023d7f3e6c | ||
|
|
01285ad4cd | ||
|
|
0d855c1e9c | ||
|
|
2d21c00e8d | ||
|
|
de9d0eb895 | ||
|
|
9998916ef6 | ||
|
|
453123a8ed | ||
|
|
2396cad2d1 | ||
|
|
540eecc207 | ||
|
|
b3a7c20f3f | ||
|
|
2fc4115718 | ||
|
|
c6c027dbec | ||
|
|
60edb11420 | ||
|
|
51ce3ffd36 | ||
|
|
af7bfac1e2 | ||
|
|
054320820c | ||
|
|
1a52b0ee3b | ||
|
|
78f3af6fc1 | ||
|
|
b5b98d6e19 | ||
|
|
aa1b71e431 | ||
|
|
472c0ff2fb | ||
|
|
58bedd4c1a | ||
|
|
88790dcaee | ||
|
|
a8fb41333b | ||
|
|
8d5464692f | ||
|
|
2080e0460f | ||
|
|
7f5fc798c5 | ||
|
|
833cb03ee1 | ||
|
|
590de9c890 | ||
|
|
583d106104 | ||
|
|
7ada2cf89a | ||
|
|
9f79dccb05 | ||
|
|
69e6e79e23 | ||
|
|
3292f760ca |
+15
-1
@@ -4,11 +4,22 @@ sudo: required
|
|||||||
|
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
|
dist: "trusty"
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- '2.7'
|
- '2.7'
|
||||||
- 'pypy'
|
|
||||||
- '3.5'
|
- '3.5'
|
||||||
- '3.6'
|
- '3.6'
|
||||||
|
- '3.6-dev'
|
||||||
|
- '3.7-dev'
|
||||||
|
- 'pypy-5.3.1'
|
||||||
|
- 'pypy3.5-5.7.1-beta'
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- python: 'pypy3.5-5.7.1-beta'
|
||||||
|
- python: '3.6-dev'
|
||||||
|
- python: '3.7-dev'
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install -e .
|
- pip install -e .
|
||||||
@@ -33,3 +44,6 @@ notifications:
|
|||||||
|
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.4"
|
postgresql: "9.4"
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- postgresql-9.4-postgis-2.3
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
## 2.16.0b1
|
## 2.16.1
|
||||||
|
- pydal 17.11
|
||||||
|
- bootstrap 4
|
||||||
|
- better welcome examples
|
||||||
|
- many bug fixes
|
||||||
|
|
||||||
|
## 2.15.1-4
|
||||||
|
- pydal 17.08
|
||||||
|
- dropped support for python 2.6
|
||||||
|
- dropped web shell
|
||||||
- experimental python 3 support
|
- experimental python 3 support
|
||||||
- experimental authapi for service login
|
- experimental authapi for service login
|
||||||
|
- allow ajax file uploads
|
||||||
- more tests
|
- more tests
|
||||||
|
- more pep8 compliance
|
||||||
- d3.js model visulization
|
- d3.js model visulization
|
||||||
- improved scheduler
|
- improved scheduler
|
||||||
- is_email support for internationalized Domain Names
|
- is_email support for internationalized Domain Names
|
||||||
@@ -20,6 +31,13 @@
|
|||||||
- Updated fpdf to latest version
|
- Updated fpdf to latest version
|
||||||
- JWT support
|
- JWT support
|
||||||
- import fabfile for remote deployment
|
- import fabfile for remote deployment
|
||||||
|
- scheduler new feature: you can now specify intervals with cron
|
||||||
|
- gluon/* removed from sys.path. Applications relying on statements like e.g.
|
||||||
|
"from storage import Storage"
|
||||||
|
will need to be rewritten with
|
||||||
|
"from gluon.storage import Storage"
|
||||||
|
- tests can only be run with the usual web2py.py --run_system_tests OR with
|
||||||
|
python -m unittest -v gluon.tests on the root dir
|
||||||
- jQuery 3.2.1
|
- jQuery 3.2.1
|
||||||
- PyDAL 17.07 including:
|
- PyDAL 17.07 including:
|
||||||
allow jsonb support for postgres
|
allow jsonb support for postgres
|
||||||
@@ -31,20 +49,7 @@
|
|||||||
improved mongodb support
|
improved mongodb support
|
||||||
overall refactoring
|
overall refactoring
|
||||||
experimental support for Google Cloud SQL v2
|
experimental support for Google Cloud SQL v2
|
||||||
|
new pymysql driver
|
||||||
## 2.15.x
|
|
||||||
- web2py does not support python 2.6 anymore
|
|
||||||
- py3.5 syntax compatible (see #1353 for details)
|
|
||||||
- dropped web shell from admin
|
|
||||||
- scheduler new feature: you can now specify intervals with cron
|
|
||||||
- gluon/* removed from sys.path. Applications relying on statements like e.g.
|
|
||||||
"from storage import Storage"
|
|
||||||
will need to be rewritten with
|
|
||||||
"from gluon.storage import Storage"
|
|
||||||
- tests can only be run with the usual web2py.py --run_system_tests OR with
|
|
||||||
python -m unittest -v gluon.tests on the root dir
|
|
||||||
- updated pymysql driver
|
|
||||||
|
|
||||||
|
|
||||||
## 2.14.6
|
## 2.14.6
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,7 @@ update:
|
|||||||
wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py
|
wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py
|
||||||
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
|
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
|
||||||
echo "remember that pymysql was tweaked"
|
echo "remember that pymysql was tweaked"
|
||||||
src:
|
rmfiles:
|
||||||
### Use semantic versioning
|
|
||||||
echo 'Version 2.16.0-beta2+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
|
||||||
### rm -f all junk files
|
|
||||||
make clean
|
|
||||||
### clean up baisc apps
|
### clean up baisc apps
|
||||||
rm -f routes.py
|
rm -f routes.py
|
||||||
rm -rf applications/*/sessions/*
|
rm -rf applications/*/sessions/*
|
||||||
@@ -46,6 +42,12 @@ src:
|
|||||||
rm -rf applications/admin/uploads/*
|
rm -rf applications/admin/uploads/*
|
||||||
rm -rf applications/welcome/uploads/*
|
rm -rf applications/welcome/uploads/*
|
||||||
rm -rf applications/examples/uploads/*
|
rm -rf applications/examples/uploads/*
|
||||||
|
src:
|
||||||
|
### Use semantic versioning
|
||||||
|
echo 'Version 2.16.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||||
|
### rm -f all junk files
|
||||||
|
#make clean
|
||||||
|
# make rmfiles
|
||||||
### make welcome layout and appadmin the default
|
### make welcome layout and appadmin the default
|
||||||
cp applications/welcome/views/appadmin.html applications/admin/views
|
cp applications/welcome/views/appadmin.html applications/admin/views
|
||||||
cp applications/welcome/views/appadmin.html applications/examples/views
|
cp applications/welcome/views/appadmin.html applications/examples/views
|
||||||
@@ -54,7 +56,7 @@ src:
|
|||||||
### build web2py_src.zip
|
### build web2py_src.zip
|
||||||
echo '' > NEWINSTALL
|
echo '' > NEWINSTALL
|
||||||
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
||||||
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
cd ..; zip -r --exclude=**.git** web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||||
|
|
||||||
mdp:
|
mdp:
|
||||||
make src
|
make src
|
||||||
@@ -97,6 +99,8 @@ win:
|
|||||||
cp -r applications/welcome ../web2py_win/web2py/applications
|
cp -r applications/welcome ../web2py_win/web2py/applications
|
||||||
cp -r applications/examples ../web2py_win/web2py/applications
|
cp -r applications/examples ../web2py_win/web2py/applications
|
||||||
cp applications/__init__.py ../web2py_win/web2py/applications
|
cp applications/__init__.py ../web2py_win/web2py/applications
|
||||||
|
# per https://github.com/web2py/web2py/issues/1716
|
||||||
|
mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done'
|
||||||
cd ../web2py_win; zip -r web2py_win.zip web2py
|
cd ../web2py_win; zip -r web2py_win.zip web2py
|
||||||
mv ../web2py_win/web2py_win.zip .
|
mv ../web2py_win/web2py_win.zip .
|
||||||
run:
|
run:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.16.0-beta2+timestamp.2017.07.10.03.14.16
|
Version 2.16.1-stable+timestamp.2017.11.13.23.50.07
|
||||||
|
|||||||
@@ -657,37 +657,36 @@ def d3_graph_model():
|
|||||||
Create a list of table dicts, called "nodes"
|
Create a list of table dicts, called "nodes"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = {}
|
|
||||||
nodes = []
|
nodes = []
|
||||||
links = []
|
links = []
|
||||||
|
|
||||||
subgraphs = dict()
|
for database in databases:
|
||||||
|
db = eval_in_global_env(database)
|
||||||
|
for tablename in db.tables:
|
||||||
|
fields = []
|
||||||
|
for field in db[tablename]:
|
||||||
|
f_type = field.type
|
||||||
|
if not isinstance(f_type,str):
|
||||||
|
disp = ' '
|
||||||
|
elif f_type == 'string':
|
||||||
|
disp = field.length
|
||||||
|
elif f_type == 'id':
|
||||||
|
disp = "PK"
|
||||||
|
elif f_type.startswith('reference') or \
|
||||||
|
f_type.startswith('list:reference'):
|
||||||
|
disp = "FK"
|
||||||
|
else:
|
||||||
|
disp = ' '
|
||||||
|
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
||||||
|
|
||||||
for tablename in db.tables:
|
if isinstance(f_type,str) and (
|
||||||
fields = []
|
f_type.startswith('reference') or
|
||||||
for field in db[tablename]:
|
f_type.startswith('list:reference')):
|
||||||
f_type = field.type
|
referenced_table = f_type.split()[1].split('.')[0]
|
||||||
if not isinstance(f_type,str):
|
|
||||||
disp = ' '
|
|
||||||
elif f_type == 'string':
|
|
||||||
disp = field.length
|
|
||||||
elif f_type == 'id':
|
|
||||||
disp = "PK"
|
|
||||||
elif f_type.startswith('reference') or \
|
|
||||||
f_type.startswith('list:reference'):
|
|
||||||
disp = "FK"
|
|
||||||
else:
|
|
||||||
disp = ' '
|
|
||||||
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
|
||||||
|
|
||||||
if isinstance(f_type,str) and (
|
links.append(dict(source=tablename, target = referenced_table))
|
||||||
f_type.startswith('reference') or
|
|
||||||
f_type.startswith('list:reference')):
|
|
||||||
referenced_table = f_type.split()[1].split('.')[0]
|
|
||||||
|
|
||||||
links.append(dict(source=tablename, target = referenced_table))
|
nodes.append(dict(name=tablename, type="table", fields = fields))
|
||||||
|
|
||||||
nodes.append(dict(name=tablename, type="table", fields = fields))
|
|
||||||
|
|
||||||
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
||||||
response.files.append(URL('admin','static','js/d3.min.js'))
|
response.files.append(URL('admin','static','js/d3.min.js'))
|
||||||
|
|||||||
@@ -963,8 +963,7 @@ def edit_language():
|
|||||||
form = SPAN(strings['__corrupted__'], _class='error')
|
form = SPAN(strings['__corrupted__'], _class='error')
|
||||||
return dict(filename=filename, form=form)
|
return dict(filename=filename, form=form)
|
||||||
|
|
||||||
keys = sorted(strings.keys(), lambda x, y: cmp(
|
keys = sorted(strings.keys(), key=lambda x: to_native(x).lower())
|
||||||
unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower()))
|
|
||||||
rows = []
|
rows = []
|
||||||
rows.append(H2(T('Original/Translation')))
|
rows.append(H2(T('Original/Translation')))
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import re
|
|||||||
import gzip
|
import gzip
|
||||||
import tarfile
|
import tarfile
|
||||||
from gluon.contrib.simplejsonrpc import ServerProxy
|
from gluon.contrib.simplejsonrpc import ServerProxy
|
||||||
from gluon._compat import StringIO, ProtocolError
|
from gluon._compat import StringIO, ProtocolError, urlencode, urllib2
|
||||||
|
|
||||||
def deploy():
|
def deploy():
|
||||||
response.title = T('Deploy to pythonanywhere')
|
response.title = T('Deploy to pythonanywhere')
|
||||||
@@ -26,9 +26,8 @@ def create_account():
|
|||||||
except ProtocolError as error:
|
except ProtocolError as error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
import urllib, urllib2
|
|
||||||
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
|
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
|
||||||
data = urllib.urlencode(request.vars)
|
data = urlencode(request.vars)
|
||||||
req = urllib2.Request(url, data)
|
req = urllib2.Request(url, data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+4
-4
File diff suppressed because one or more lines are too long
@@ -12,6 +12,10 @@
|
|||||||
$.error('web2py.js has already been loaded!');
|
$.error('web2py.js has already been loaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||||
|
var animateIn = 'fadeIn';
|
||||||
|
// var animateIn = 'slideDown';
|
||||||
|
|
||||||
String.prototype.reverse = function () {
|
String.prototype.reverse = function () {
|
||||||
return this.split('').reverse().join('');
|
return this.split('').reverse().join('');
|
||||||
};
|
};
|
||||||
@@ -178,7 +182,7 @@
|
|||||||
},
|
},
|
||||||
/* manage errors in forms */
|
/* manage errors in forms */
|
||||||
manage_errors: function (target) {
|
manage_errors: function (target) {
|
||||||
$('div.error', target).hide().slideDown('slow');
|
$('div.error', target).hide()[animateIn]('slow');
|
||||||
},
|
},
|
||||||
after_ajax: function (xhr) {
|
after_ajax: function (xhr) {
|
||||||
/* called whenever an ajax request completes */
|
/* called whenever an ajax request completes */
|
||||||
@@ -263,13 +267,17 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
/* help preventing double form submission for normal form (not LOADed) */
|
/* help preventing double form submission for normal form (not LOADed) */
|
||||||
$(doc).on('submit', 'form', function () {
|
$(doc).on('submit', 'form', function (e) {
|
||||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||||
web2py.disableElement(submit_button);
|
submit_buttons.each(function() {
|
||||||
|
web2py.disableElement($(this));
|
||||||
|
})
|
||||||
/* safeguard in case the form doesn't trigger a refresh,
|
/* safeguard in case the form doesn't trigger a refresh,
|
||||||
see https://github.com/web2py/web2py/issues/1100 */
|
see https://github.com/web2py/web2py/issues/1100 */
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
web2py.enableElement(submit_button);
|
submit_buttons.each(function() {
|
||||||
|
web2py.enableElement($(this));
|
||||||
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
doc.ajaxSuccess(function (e, xhr) {
|
doc.ajaxSuccess(function (e, xhr) {
|
||||||
@@ -320,7 +328,15 @@
|
|||||||
form.submit(function (e) {
|
form.submit(function (e) {
|
||||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
|
||||||
|
var formData;
|
||||||
|
if (FORMDATA_IS_SUPPORTED) {
|
||||||
|
formData = new FormData(form[0]); // Allows file uploads.
|
||||||
|
} else {
|
||||||
|
formData = form.serialize(); // Fallback for older browsers.
|
||||||
|
}
|
||||||
|
web2py.ajax_page('post', url, formData, target, form);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||||
@@ -339,11 +355,18 @@
|
|||||||
if (web2py.isUndefined(element)) element = $(document);
|
if (web2py.isUndefined(element)) element = $(document);
|
||||||
/* if target is not there, fill it with something that there isn't in the page*/
|
/* if target is not there, fill it with something that there isn't in the page*/
|
||||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||||
|
|
||||||
|
/* processData and contentType must be set to false when passing a FormData
|
||||||
|
object to jQuery.ajax. */
|
||||||
|
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||||
|
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||||
$.ajax({
|
$.ajax({
|
||||||
'type': method,
|
'type': method,
|
||||||
'url': action,
|
'url': action,
|
||||||
'data': data,
|
'data': data,
|
||||||
|
'processData': !isFormData,
|
||||||
|
'contentType': contentType,
|
||||||
'beforeSend': function (xhr, settings) {
|
'beforeSend': function (xhr, settings) {
|
||||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||||
xhr.setRequestHeader('web2py-component-element', target);
|
xhr.setRequestHeader('web2py-component-element', target);
|
||||||
@@ -594,8 +617,8 @@
|
|||||||
flash: function (message, status) {
|
flash: function (message, status) {
|
||||||
var flash = $('.w2p_flash');
|
var flash = $('.w2p_flash');
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
flash.html(message).addClass(status);
|
flash.text(message).addClass(status);
|
||||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
if (flash.html()) flash.append('<span id="closeflash"> × </span>')[animateIn]();
|
||||||
},
|
},
|
||||||
hide_flash: function () {
|
hide_flash: function () {
|
||||||
$('.w2p_flash').fadeOut(0).html('');
|
$('.w2p_flash').fadeOut(0).html('');
|
||||||
@@ -609,7 +632,7 @@
|
|||||||
for (var k = 0; k < triggers[id].length; k++) {
|
for (var k = 0; k < triggers[id].length; k++) {
|
||||||
var dep = $('#' + triggers[id][k], target);
|
var dep = $('#' + triggers[id][k], target);
|
||||||
var tr = $('#' + triggers[id][k] + '__row', target);
|
var tr = $('#' + triggers[id][k] + '__row', target);
|
||||||
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
|
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
|
||||||
else tr.hide();
|
else tr.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -699,8 +722,9 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Disables form elements:
|
/* Disables form elements:
|
||||||
|
- Does not disable elements with 'data-w2p_disable' attribute
|
||||||
- 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
|
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||||
- Sets disabled property to true
|
- Sets disabled property to true
|
||||||
*/
|
*/
|
||||||
disableFormElements: function (form) {
|
disableFormElements: function (form) {
|
||||||
@@ -712,13 +736,15 @@
|
|||||||
if (!web2py.isUndefined(disable)) {
|
if (!web2py.isUndefined(disable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(disable_with)) {
|
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||||
element.data('w2p_disable_with', element[method]());
|
if (web2py.isUndefined(disable_with)) {
|
||||||
|
element.data('w2p_disable_with', element[method]());
|
||||||
|
}
|
||||||
|
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||||
|
element.data('w2p_enable_with', element[method]());
|
||||||
|
}
|
||||||
|
element[method](element.data('w2p_disable_with'));
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
|
||||||
element.data('w2p_enable_with', element[method]());
|
|
||||||
}
|
|
||||||
element[method](element.data('w2p_disable_with'));
|
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
|||||||
web2py_trap_link = jQuery.web2py.trap_link;
|
web2py_trap_link = jQuery.web2py.trap_link;
|
||||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||||
*/
|
*/
|
||||||
/* compatibility code - end*/
|
/* compatibility code - end*/
|
||||||
|
|||||||
@@ -239,7 +239,7 @@
|
|||||||
{{=T("No databases in this application")}}
|
{{=T("No databases in this application")}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<div id="vis"></div>
|
<div id="vis"></div>
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/d3_graph.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||||
<script>
|
<script>
|
||||||
// Define the d3 input data
|
// Define the d3 input data
|
||||||
{{from gluon.serializers import json }}
|
{{from gluon.serializers import json }}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
<a class="btn dropdown-toggle" data-toggle="dropdown">
|
||||||
{{=T('Manage')}}
|
{{=T('Manage')}}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -657,37 +657,36 @@ def d3_graph_model():
|
|||||||
Create a list of table dicts, called "nodes"
|
Create a list of table dicts, called "nodes"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = {}
|
|
||||||
nodes = []
|
nodes = []
|
||||||
links = []
|
links = []
|
||||||
|
|
||||||
subgraphs = dict()
|
for database in databases:
|
||||||
|
db = eval_in_global_env(database)
|
||||||
|
for tablename in db.tables:
|
||||||
|
fields = []
|
||||||
|
for field in db[tablename]:
|
||||||
|
f_type = field.type
|
||||||
|
if not isinstance(f_type,str):
|
||||||
|
disp = ' '
|
||||||
|
elif f_type == 'string':
|
||||||
|
disp = field.length
|
||||||
|
elif f_type == 'id':
|
||||||
|
disp = "PK"
|
||||||
|
elif f_type.startswith('reference') or \
|
||||||
|
f_type.startswith('list:reference'):
|
||||||
|
disp = "FK"
|
||||||
|
else:
|
||||||
|
disp = ' '
|
||||||
|
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
||||||
|
|
||||||
for tablename in db.tables:
|
if isinstance(f_type,str) and (
|
||||||
fields = []
|
f_type.startswith('reference') or
|
||||||
for field in db[tablename]:
|
f_type.startswith('list:reference')):
|
||||||
f_type = field.type
|
referenced_table = f_type.split()[1].split('.')[0]
|
||||||
if not isinstance(f_type,str):
|
|
||||||
disp = ' '
|
|
||||||
elif f_type == 'string':
|
|
||||||
disp = field.length
|
|
||||||
elif f_type == 'id':
|
|
||||||
disp = "PK"
|
|
||||||
elif f_type.startswith('reference') or \
|
|
||||||
f_type.startswith('list:reference'):
|
|
||||||
disp = "FK"
|
|
||||||
else:
|
|
||||||
disp = ' '
|
|
||||||
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
|
||||||
|
|
||||||
if isinstance(f_type,str) and (
|
links.append(dict(source=tablename, target = referenced_table))
|
||||||
f_type.startswith('reference') or
|
|
||||||
f_type.startswith('list:reference')):
|
|
||||||
referenced_table = f_type.split()[1].split('.')[0]
|
|
||||||
|
|
||||||
links.append(dict(source=tablename, target = referenced_table))
|
nodes.append(dict(name=tablename, type="table", fields = fields))
|
||||||
|
|
||||||
nodes.append(dict(name=tablename, type="table", fields = fields))
|
|
||||||
|
|
||||||
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
||||||
response.files.append(URL('admin','static','js/d3.min.js'))
|
response.files.append(URL('admin','static','js/d3.min.js'))
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
- **Created by a community of professionals** and University professors in Computer Science and Software Engineering.
|
- **Created by a community of professionals** and University professors in Computer Science and Software Engineering.
|
||||||
- **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future.
|
- **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future.
|
||||||
- **Easy to run.** It requires no installation and no configuration.
|
- **Easy to run.** It requires no installation and no configuration.
|
||||||
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.5/2.6/2.7/pypy, or Java with Jython.
|
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.7/3.5/3.6/pypy.
|
||||||
- **Runs with** Apache, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
|
- **Runs with** Apache, Nginx, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
|
||||||
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Oracle, IBM DB2, Informix, Ingres, and Google App Engine.
|
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Sybase, Oracle, IBM DB2, Informix, Ingres, MongoDB, and Google App Engine.
|
||||||
- **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution.
|
- **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution.
|
||||||
- **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable.
|
- **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable.
|
||||||
- **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF).
|
- **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF).
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
$.error('web2py.js has already been loaded!');
|
$.error('web2py.js has already been loaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||||
|
var animateIn = 'fadeIn';
|
||||||
|
// var animateIn = 'slideDown';
|
||||||
|
|
||||||
String.prototype.reverse = function () {
|
String.prototype.reverse = function () {
|
||||||
return this.split('').reverse().join('');
|
return this.split('').reverse().join('');
|
||||||
};
|
};
|
||||||
@@ -178,7 +182,7 @@
|
|||||||
},
|
},
|
||||||
/* manage errors in forms */
|
/* manage errors in forms */
|
||||||
manage_errors: function (target) {
|
manage_errors: function (target) {
|
||||||
$('div.error', target).hide().slideDown('slow');
|
$('div.error', target).hide()[animateIn]('slow');
|
||||||
},
|
},
|
||||||
after_ajax: function (xhr) {
|
after_ajax: function (xhr) {
|
||||||
/* called whenever an ajax request completes */
|
/* called whenever an ajax request completes */
|
||||||
@@ -263,13 +267,17 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
/* help preventing double form submission for normal form (not LOADed) */
|
/* help preventing double form submission for normal form (not LOADed) */
|
||||||
$(doc).on('submit', 'form', function () {
|
$(doc).on('submit', 'form', function (e) {
|
||||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||||
web2py.disableElement(submit_button);
|
submit_buttons.each(function() {
|
||||||
|
web2py.disableElement($(this));
|
||||||
|
})
|
||||||
/* safeguard in case the form doesn't trigger a refresh,
|
/* safeguard in case the form doesn't trigger a refresh,
|
||||||
see https://github.com/web2py/web2py/issues/1100 */
|
see https://github.com/web2py/web2py/issues/1100 */
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
web2py.enableElement(submit_button);
|
submit_buttons.each(function() {
|
||||||
|
web2py.enableElement($(this));
|
||||||
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
doc.ajaxSuccess(function (e, xhr) {
|
doc.ajaxSuccess(function (e, xhr) {
|
||||||
@@ -320,7 +328,15 @@
|
|||||||
form.submit(function (e) {
|
form.submit(function (e) {
|
||||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
|
||||||
|
var formData;
|
||||||
|
if (FORMDATA_IS_SUPPORTED) {
|
||||||
|
formData = new FormData(form[0]); // Allows file uploads.
|
||||||
|
} else {
|
||||||
|
formData = form.serialize(); // Fallback for older browsers.
|
||||||
|
}
|
||||||
|
web2py.ajax_page('post', url, formData, target, form);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||||
@@ -339,11 +355,18 @@
|
|||||||
if (web2py.isUndefined(element)) element = $(document);
|
if (web2py.isUndefined(element)) element = $(document);
|
||||||
/* if target is not there, fill it with something that there isn't in the page*/
|
/* if target is not there, fill it with something that there isn't in the page*/
|
||||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||||
|
|
||||||
|
/* processData and contentType must be set to false when passing a FormData
|
||||||
|
object to jQuery.ajax. */
|
||||||
|
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||||
|
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||||
$.ajax({
|
$.ajax({
|
||||||
'type': method,
|
'type': method,
|
||||||
'url': action,
|
'url': action,
|
||||||
'data': data,
|
'data': data,
|
||||||
|
'processData': !isFormData,
|
||||||
|
'contentType': contentType,
|
||||||
'beforeSend': function (xhr, settings) {
|
'beforeSend': function (xhr, settings) {
|
||||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||||
xhr.setRequestHeader('web2py-component-element', target);
|
xhr.setRequestHeader('web2py-component-element', target);
|
||||||
@@ -594,8 +617,8 @@
|
|||||||
flash: function (message, status) {
|
flash: function (message, status) {
|
||||||
var flash = $('.w2p_flash');
|
var flash = $('.w2p_flash');
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
flash.html(message).addClass(status);
|
flash.text(message).addClass(status);
|
||||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
if (flash.html()) flash.append('<span id="closeflash"> × </span>')[animateIn]();
|
||||||
},
|
},
|
||||||
hide_flash: function () {
|
hide_flash: function () {
|
||||||
$('.w2p_flash').fadeOut(0).html('');
|
$('.w2p_flash').fadeOut(0).html('');
|
||||||
@@ -609,7 +632,7 @@
|
|||||||
for (var k = 0; k < triggers[id].length; k++) {
|
for (var k = 0; k < triggers[id].length; k++) {
|
||||||
var dep = $('#' + triggers[id][k], target);
|
var dep = $('#' + triggers[id][k], target);
|
||||||
var tr = $('#' + triggers[id][k] + '__row', target);
|
var tr = $('#' + triggers[id][k] + '__row', target);
|
||||||
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
|
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
|
||||||
else tr.hide();
|
else tr.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -699,8 +722,9 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Disables form elements:
|
/* Disables form elements:
|
||||||
|
- Does not disable elements with 'data-w2p_disable' attribute
|
||||||
- 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
|
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||||
- Sets disabled property to true
|
- Sets disabled property to true
|
||||||
*/
|
*/
|
||||||
disableFormElements: function (form) {
|
disableFormElements: function (form) {
|
||||||
@@ -712,13 +736,15 @@
|
|||||||
if (!web2py.isUndefined(disable)) {
|
if (!web2py.isUndefined(disable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(disable_with)) {
|
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||||
element.data('w2p_disable_with', element[method]());
|
if (web2py.isUndefined(disable_with)) {
|
||||||
|
element.data('w2p_disable_with', element[method]());
|
||||||
|
}
|
||||||
|
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||||
|
element.data('w2p_enable_with', element[method]());
|
||||||
|
}
|
||||||
|
element[method](element.data('w2p_disable_with'));
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
|
||||||
element.data('w2p_enable_with', element[method]());
|
|
||||||
}
|
|
||||||
element[method](element.data('w2p_disable_with'));
|
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
|||||||
web2py_trap_link = jQuery.web2py.trap_link;
|
web2py_trap_link = jQuery.web2py.trap_link;
|
||||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||||
*/
|
*/
|
||||||
/* compatibility code - end*/
|
/* compatibility code - end*/
|
||||||
|
|||||||
@@ -239,7 +239,7 @@
|
|||||||
{{=T("No databases in this application")}}
|
{{=T("No databases in this application")}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<div id="vis"></div>
|
<div id="vis"></div>
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/d3_graph.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||||
<script>
|
<script>
|
||||||
// Define the d3 input data
|
// Define the d3 input data
|
||||||
{{from gluon.serializers import json }}
|
{{from gluon.serializers import json }}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
|
<a class="btn btn180 rounded green" href="http://mdipierro.github.io/web2py/web2py_manual_5th.pdf">Manual</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
|
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
|
||||||
|
|||||||
@@ -392,12 +392,11 @@ def ccache():
|
|||||||
cache.disk.clear()
|
cache.disk.clear()
|
||||||
session.flash += T("Disk Cleared")
|
session.flash += T("Disk Cleared")
|
||||||
redirect(URL(r=request))
|
redirect(URL(r=request))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from guppy import hpy
|
from pympler.asizeof import asizeof
|
||||||
hp = hpy()
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
hp = False
|
asizeof = False
|
||||||
|
|
||||||
import shelve
|
import shelve
|
||||||
import os
|
import os
|
||||||
@@ -451,9 +450,9 @@ def ccache():
|
|||||||
ram['ratio'] = 0
|
ram['ratio'] = 0
|
||||||
|
|
||||||
for key, value in iteritems(cache.ram.storage):
|
for key, value in iteritems(cache.ram.storage):
|
||||||
if hp:
|
if asizeof:
|
||||||
ram['bytes'] += hp.iso(value[1]).size
|
ram['bytes'] += asizeof(value[1])
|
||||||
ram['objects'] += hp.iso(value[1]).count
|
ram['objects'] += 1
|
||||||
ram['entries'] += 1
|
ram['entries'] += 1
|
||||||
if value[0] < ram['oldest']:
|
if value[0] < ram['oldest']:
|
||||||
ram['oldest'] = value[0]
|
ram['oldest'] = value[0]
|
||||||
@@ -469,9 +468,9 @@ def ccache():
|
|||||||
except (KeyError, ZeroDivisionError):
|
except (KeyError, ZeroDivisionError):
|
||||||
disk['ratio'] = 0
|
disk['ratio'] = 0
|
||||||
else:
|
else:
|
||||||
if hp:
|
if asizeof:
|
||||||
disk['bytes'] += hp.iso(value[1]).size
|
disk['bytes'] += asizeof(value[1])
|
||||||
disk['objects'] += hp.iso(value[1]).count
|
disk['objects'] += 1
|
||||||
disk['entries'] += 1
|
disk['entries'] += 1
|
||||||
if value[0] < disk['oldest']:
|
if value[0] < disk['oldest']:
|
||||||
disk['oldest'] = value[0]
|
disk['oldest'] = value[0]
|
||||||
@@ -511,7 +510,7 @@ def ccache():
|
|||||||
total['keys'] = key_table(total['keys'])
|
total['keys'] = key_table(total['keys'])
|
||||||
|
|
||||||
return dict(form=form, total=total,
|
return dict(form=form, total=total,
|
||||||
ram=ram, disk=disk, object_stats=hp != False)
|
ram=ram, disk=disk, object_stats=asizeof != False)
|
||||||
|
|
||||||
|
|
||||||
def table_template(table):
|
def table_template(table):
|
||||||
@@ -657,37 +656,36 @@ def d3_graph_model():
|
|||||||
Create a list of table dicts, called "nodes"
|
Create a list of table dicts, called "nodes"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = {}
|
|
||||||
nodes = []
|
nodes = []
|
||||||
links = []
|
links = []
|
||||||
|
|
||||||
subgraphs = dict()
|
for database in databases:
|
||||||
|
db = eval_in_global_env(database)
|
||||||
|
for tablename in db.tables:
|
||||||
|
fields = []
|
||||||
|
for field in db[tablename]:
|
||||||
|
f_type = field.type
|
||||||
|
if not isinstance(f_type,str):
|
||||||
|
disp = ' '
|
||||||
|
elif f_type == 'string':
|
||||||
|
disp = field.length
|
||||||
|
elif f_type == 'id':
|
||||||
|
disp = "PK"
|
||||||
|
elif f_type.startswith('reference') or \
|
||||||
|
f_type.startswith('list:reference'):
|
||||||
|
disp = "FK"
|
||||||
|
else:
|
||||||
|
disp = ' '
|
||||||
|
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
||||||
|
|
||||||
for tablename in db.tables:
|
if isinstance(f_type,str) and (
|
||||||
fields = []
|
f_type.startswith('reference') or
|
||||||
for field in db[tablename]:
|
f_type.startswith('list:reference')):
|
||||||
f_type = field.type
|
referenced_table = f_type.split()[1].split('.')[0]
|
||||||
if not isinstance(f_type,str):
|
|
||||||
disp = ' '
|
|
||||||
elif f_type == 'string':
|
|
||||||
disp = field.length
|
|
||||||
elif f_type == 'id':
|
|
||||||
disp = "PK"
|
|
||||||
elif f_type.startswith('reference') or \
|
|
||||||
f_type.startswith('list:reference'):
|
|
||||||
disp = "FK"
|
|
||||||
else:
|
|
||||||
disp = ' '
|
|
||||||
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
|
||||||
|
|
||||||
if isinstance(f_type,str) and (
|
links.append(dict(source=tablename, target = referenced_table))
|
||||||
f_type.startswith('reference') or
|
|
||||||
f_type.startswith('list:reference')):
|
|
||||||
referenced_table = f_type.split()[1].split('.')[0]
|
|
||||||
|
|
||||||
links.append(dict(source=tablename, target = referenced_table))
|
nodes.append(dict(name=tablename, type="table", fields = fields))
|
||||||
|
|
||||||
nodes.append(dict(name=tablename, type="table", fields = fields))
|
|
||||||
|
|
||||||
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
||||||
response.files.append(URL('admin','static','js/d3.min.js'))
|
response.files.append(URL('admin','static','js/d3.min.js'))
|
||||||
|
|||||||
@@ -1,26 +1,35 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# this file is released under public domain and you can use without limitations
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# This is a sample controller
|
# This is a sample controller
|
||||||
# - index is the default action of any application
|
# this file is released under public domain and you can use without limitations
|
||||||
# - user is required for authentication and authorization
|
|
||||||
# - download is for downloading files uploaded in the db (does streaming)
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ---- example index page ----
|
||||||
def index():
|
def index():
|
||||||
"""
|
|
||||||
example action using the internationalization operator T and flash
|
|
||||||
rendered by views/default/index.html or views/generic.html
|
|
||||||
|
|
||||||
if you need a simple wiki simply replace the two lines below with:
|
|
||||||
return auth.wiki()
|
|
||||||
"""
|
|
||||||
response.flash = T("Hello World")
|
response.flash = T("Hello World")
|
||||||
return dict(message=T('Welcome to web2py!'))
|
return dict(message=T('Welcome to web2py!'))
|
||||||
|
|
||||||
|
# ---- API (example) -----
|
||||||
|
@auth.requires_login()
|
||||||
|
def api_get_user_email():
|
||||||
|
if not request.env.request_method == 'GET': raise HTTP(403)
|
||||||
|
return response.json({'status':'success', 'email':auth.user.email})
|
||||||
|
|
||||||
|
# ---- Smart Grid (example) -----
|
||||||
|
@auth.requires_membership('admin') # can only be accessed by members of admin groupd
|
||||||
|
def grid():
|
||||||
|
response.view = 'generic.html' # use a generic view
|
||||||
|
tablename = request.args(0)
|
||||||
|
if not tablename in db.tables: raise HTTP(403)
|
||||||
|
grid = SQLFORM.smartgrid(db[tablename], args=[tablename], deletable=False, editable=False)
|
||||||
|
return dict(grid=grid)
|
||||||
|
|
||||||
|
# ---- Embedded wiki (example) ----
|
||||||
|
def wiki():
|
||||||
|
auth.wikimenu() # add the wiki to the menu
|
||||||
|
return auth.wiki()
|
||||||
|
|
||||||
|
# ---- Action for login/register/etc (required for auth) -----
|
||||||
def user():
|
def user():
|
||||||
"""
|
"""
|
||||||
exposes:
|
exposes:
|
||||||
@@ -39,7 +48,7 @@ def user():
|
|||||||
"""
|
"""
|
||||||
return dict(form=auth())
|
return dict(form=auth())
|
||||||
|
|
||||||
|
# ---- action to server uploaded static content (required) ---
|
||||||
@cache.action()
|
@cache.action()
|
||||||
def download():
|
def download():
|
||||||
"""
|
"""
|
||||||
@@ -47,15 +56,3 @@ def download():
|
|||||||
http://..../[app]/default/download/[filename]
|
http://..../[app]/default/download/[filename]
|
||||||
"""
|
"""
|
||||||
return response.download(request, db)
|
return response.download(request, db)
|
||||||
|
|
||||||
|
|
||||||
def call():
|
|
||||||
"""
|
|
||||||
exposes services. for example:
|
|
||||||
http://..../[app]/default/call/jsonrpc
|
|
||||||
decorate with @services.jsonrpc the functions to expose
|
|
||||||
supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
|
|
||||||
"""
|
|
||||||
return service()
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
'%s %%{row} deleted': '%s %%{fila} %%{eliminada}',
|
'%s %%{row} deleted': '%s %%{fila} %%{eliminada}',
|
||||||
'%s %%{row} updated': '%s %%{fila} %%{actualizada}',
|
'%s %%{row} updated': '%s %%{fila} %%{actualizada}',
|
||||||
'%s selected': '%s %%{seleccionado}',
|
'%s selected': '%s %%{seleccionado}',
|
||||||
'%Y-%m-%d': '%d/%m/%A',
|
'%Y-%m-%d': '%d/%m/%Y',
|
||||||
'%Y-%m-%d %H:%M:%S': '%d/%m/%A %H:%M:%S',
|
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||||
'(**%.0d MB**)': '(**%.0d MB**)',
|
'(**%.0d MB**)': '(**%.0d MB**)',
|
||||||
'(something like "it-it")': '(algo como "it-it")',
|
'(something like "it-it")': '(algo como "it-it")',
|
||||||
'**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}',
|
'**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}',
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# AppConfig configuration made easy. Look inside private/appconfig.ini
|
||||||
|
# Auth is for authenticaiton and access control
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
from gluon.contrib.appconfig import AppConfig
|
||||||
|
from gluon.tools import Auth
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# This scaffolding model makes your app work on Google App Engine too
|
# This scaffolding model makes your app work on Google App Engine too
|
||||||
# File is released under public domain and you can use without limitations
|
# File is released under public domain and you can use without limitations
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
if request.global_settings.web2py_version < "2.14.1":
|
if request.global_settings.web2py_version < "2.15.5":
|
||||||
raise HTTP(500, "Requires web2py 2.13.3 or newer")
|
raise HTTP(500, "Requires web2py 2.15.5 or newer")
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# if SSL/HTTPS is properly configured and you want all HTTP requests to
|
# if SSL/HTTPS is properly configured and you want all HTTP requests to
|
||||||
@@ -14,23 +21,18 @@ if request.global_settings.web2py_version < "2.14.1":
|
|||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# request.requires_https()
|
# request.requires_https()
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# app configuration made easy. Look inside private/appconfig.ini
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
from gluon.contrib.appconfig import AppConfig
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# once in production, remove reload=True to gain full speed
|
# once in production, remove reload=True to gain full speed
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
myconf = AppConfig(reload=True)
|
configuration = AppConfig(reload=True)
|
||||||
|
|
||||||
if not request.env.web2py_runtime_gae:
|
if not request.env.web2py_runtime_gae:
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# if NOT running on Google App Engine use SQLite or other DB
|
# if NOT running on Google App Engine use SQLite or other DB
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
db = DAL(myconf.get('db.uri'),
|
db = DAL(configuration.get('db.uri'),
|
||||||
pool_size=myconf.get('db.pool_size'),
|
pool_size=configuration.get('db.pool_size'),
|
||||||
migrate_enabled=myconf.get('db.migrate'),
|
migrate_enabled=configuration.get('db.migrate'),
|
||||||
check_reserved=['all'])
|
check_reserved=['all'])
|
||||||
else:
|
else:
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
@@ -52,12 +54,15 @@ else:
|
|||||||
# by default give a view/generic.extension to all actions from localhost
|
# by default give a view/generic.extension to all actions from localhost
|
||||||
# none otherwise. a pattern can be 'controller/function.extension'
|
# none otherwise. a pattern can be 'controller/function.extension'
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
response.generic_patterns = ['*'] if request.is_local else []
|
response.generic_patterns = []
|
||||||
|
if request.is_local and not configuration.get('app.production'):
|
||||||
|
response.generic_patterns.append('*')
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# choose a style for forms
|
# choose a style for forms
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
|
response.formstyle = 'bootstrap4_inline'
|
||||||
response.form_label_separator = myconf.get('forms.separator') or ''
|
response.form_label_separator = ''
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# (optional) optimize handling of static files
|
# (optional) optimize handling of static files
|
||||||
@@ -80,27 +85,24 @@ response.form_label_separator = myconf.get('forms.separator') or ''
|
|||||||
# (more options discussed in gluon/tools.py)
|
# (more options discussed in gluon/tools.py)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
from gluon.tools import Auth, Service, PluginManager
|
|
||||||
|
|
||||||
# host names must be a list of allowed host names (glob syntax allowed)
|
# host names must be a list of allowed host names (glob syntax allowed)
|
||||||
auth = Auth(db, host_names=myconf.get('host.names'))
|
auth = Auth(db, host_names=configuration.get('host.names'))
|
||||||
service = Service()
|
|
||||||
plugins = PluginManager()
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# create all tables needed by auth if not custom tables
|
# create all tables needed by auth, maybe add a list of extra fields
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
auth.settings.extra_fields['auth_user'] = []
|
||||||
auth.define_tables(username=False, signature=False)
|
auth.define_tables(username=False, signature=False)
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# configure email
|
# configure email
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
mail = auth.settings.mailer
|
mail = auth.settings.mailer
|
||||||
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
|
mail.settings.server = 'logging' if request.is_local else configuration.get('smtp.server')
|
||||||
mail.settings.sender = myconf.get('smtp.sender')
|
mail.settings.sender = configuration.get('smtp.sender')
|
||||||
mail.settings.login = myconf.get('smtp.login')
|
mail.settings.login = configuration.get('smtp.login')
|
||||||
mail.settings.tls = myconf.get('smtp.tls') or False
|
mail.settings.tls = configuration.get('smtp.tls') or False
|
||||||
mail.settings.ssl = myconf.get('smtp.ssl') or False
|
mail.settings.ssl = configuration.get('smtp.ssl') or False
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# configure auth policy
|
# configure auth policy
|
||||||
@@ -109,6 +111,27 @@ auth.settings.registration_requires_verification = False
|
|||||||
auth.settings.registration_requires_approval = False
|
auth.settings.registration_requires_approval = False
|
||||||
auth.settings.reset_password_requires_verification = True
|
auth.settings.reset_password_requires_verification = True
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# read more at http://dev.w3.org/html5/markup/meta.name.html
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
response.meta.author = configuration.get('app.author')
|
||||||
|
response.meta.description = configuration.get('app.description')
|
||||||
|
response.meta.keywords = configuration.get('app.keywords')
|
||||||
|
response.meta.generator = configuration.get('app.generator')
|
||||||
|
response.show_toolbar = configuration.get('app.toolbar')
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# your http://google.com/analytics id
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
response.google_analytics_id = configuration.get('google.analytics_id')
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# maybe use the scheduler
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
if configuration.get('scheduler.enabled'):
|
||||||
|
from gluon.scheduler import Scheduler
|
||||||
|
scheduler = Scheduler(db, heartbeat=configuration.get('heartbeat'))
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Define your tables below (or better in another model file) for example
|
# Define your tables below (or better in another model file) for example
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,29 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# this file is released under public domain and you can use without limitations
|
# this file is released under public domain and you can use without limitations
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
|
||||||
# Customize your APP title, subtitle and menus here
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
response.logo = A(B('web', SPAN(2), 'py'), XML('™ '),
|
|
||||||
_class="navbar-brand", _href="http://www.web2py.com/",
|
|
||||||
_id="web2py-logo")
|
|
||||||
response.title = request.application.replace('_', ' ').title()
|
|
||||||
response.subtitle = ''
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
|
||||||
# read more at http://dev.w3.org/html5/markup/meta.name.html
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
|
||||||
response.meta.author = myconf.get('app.author')
|
|
||||||
response.meta.description = myconf.get('app.description')
|
|
||||||
response.meta.keywords = myconf.get('app.keywords')
|
|
||||||
response.meta.generator = myconf.get('app.generator')
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
|
||||||
# your http://google.com/analytics id
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
|
||||||
response.google_analytics_id = None
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
# this is the main application menu add/remove items as required
|
# this is the main application menu add/remove items as required
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -32,53 +9,42 @@ response.menu = [
|
|||||||
(T('Home'), False, URL('default', 'index'), [])
|
(T('Home'), False, URL('default', 'index'), [])
|
||||||
]
|
]
|
||||||
|
|
||||||
DEVELOPMENT_MENU = True
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
# provide shortcuts for development. remove in production
|
# provide shortcuts for development. you can remove everything below in production
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def _():
|
if not configuration.get('app.production'):
|
||||||
# ------------------------------------------------------------------------------------------------------------------
|
_app = request.application
|
||||||
# shortcuts
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------
|
|
||||||
app = request.application
|
|
||||||
ctr = request.controller
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------
|
|
||||||
# useful links to internal and external resources
|
|
||||||
# ------------------------------------------------------------------------------------------------------------------
|
|
||||||
response.menu += [
|
response.menu += [
|
||||||
(T('My Sites'), False, URL('admin', 'default', 'site')),
|
(T('My Sites'), False, URL('admin', 'default', 'site')),
|
||||||
(T('This App'), False, '#', [
|
(T('This App'), False, '#', [
|
||||||
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
|
(T('Design'), False, URL('admin', 'default', 'design/%s' % _app)),
|
||||||
LI(_class="divider"),
|
|
||||||
(T('Controller'), False,
|
(T('Controller'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
|
'admin', 'default', 'edit/%s/controllers/%s.py' % (_app, request.controller))),
|
||||||
(T('View'), False,
|
(T('View'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
|
'admin', 'default', 'edit/%s/views/%s' % (_app, response.view))),
|
||||||
(T('DB Model'), False,
|
(T('DB Model'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/models/db.py' % app)),
|
'admin', 'default', 'edit/%s/models/db.py' % _app)),
|
||||||
(T('Menu Model'), False,
|
(T('Menu Model'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/models/menu.py' % app)),
|
'admin', 'default', 'edit/%s/models/menu.py' % _app)),
|
||||||
(T('Config.ini'), False,
|
(T('Config.ini'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
|
'admin', 'default', 'edit/%s/private/appconfig.ini' % _app)),
|
||||||
(T('Layout'), False,
|
(T('Layout'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/views/layout.html' % app)),
|
'admin', 'default', 'edit/%s/views/layout.html' % _app)),
|
||||||
(T('Stylesheet'), False,
|
(T('Stylesheet'), False,
|
||||||
URL(
|
URL(
|
||||||
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
|
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % _app)),
|
||||||
(T('Database'), False, URL(app, 'appadmin', 'index')),
|
(T('Database'), False, URL(_app, 'appadmin', 'index')),
|
||||||
(T('Errors'), False, URL(
|
(T('Errors'), False, URL(
|
||||||
'admin', 'default', 'errors/' + app)),
|
'admin', 'default', 'errors/' + _app)),
|
||||||
(T('About'), False, URL(
|
(T('About'), False, URL(
|
||||||
'admin', 'default', 'about/' + app)),
|
'admin', 'default', 'about/' + _app)),
|
||||||
]),
|
]),
|
||||||
('web2py.com', False, '#', [
|
('web2py.com', False, '#', [
|
||||||
(T('Download'), False,
|
(T('Download'), False,
|
||||||
@@ -98,7 +64,6 @@ def _():
|
|||||||
]),
|
]),
|
||||||
(T('Documentation'), False, '#', [
|
(T('Documentation'), False, '#', [
|
||||||
(T('Online book'), False, 'http://www.web2py.com/book'),
|
(T('Online book'), False, 'http://www.web2py.com/book'),
|
||||||
LI(_class="divider"),
|
|
||||||
(T('Preface'), False,
|
(T('Preface'), False,
|
||||||
'http://www.web2py.com/book/default/chapter/00'),
|
'http://www.web2py.com/book/default/chapter/00'),
|
||||||
(T('Introduction'), False,
|
(T('Introduction'), False,
|
||||||
@@ -143,9 +108,3 @@ def _():
|
|||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if DEVELOPMENT_MENU:
|
|
||||||
_()
|
|
||||||
|
|
||||||
if "auth" in locals():
|
|
||||||
auth.wikimenu()
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ author = Your Name <you@example.com>
|
|||||||
description = a cool new app
|
description = a cool new app
|
||||||
keywords = web2py, python, framework
|
keywords = web2py, python, framework
|
||||||
generator = Web2py Web Framework
|
generator = Web2py Web Framework
|
||||||
|
production = false
|
||||||
|
toolbar = false
|
||||||
|
|
||||||
; Host configuration
|
; Host configuration
|
||||||
[host]
|
[host]
|
||||||
@@ -14,7 +16,6 @@ names = localhost:*, 127.0.0.1:*, *:*, *
|
|||||||
[db]
|
[db]
|
||||||
uri = sqlite://storage.sqlite
|
uri = sqlite://storage.sqlite
|
||||||
migrate = true
|
migrate = true
|
||||||
; ignored for sqlite
|
|
||||||
pool_size = 10
|
pool_size = 10
|
||||||
|
|
||||||
; smtp address and credentials
|
; smtp address and credentials
|
||||||
@@ -25,7 +26,9 @@ login = username:password
|
|||||||
tls = true
|
tls = true
|
||||||
ssl = true
|
ssl = true
|
||||||
|
|
||||||
; form styling
|
[scheduler]
|
||||||
[forms]
|
enabled = false
|
||||||
formstyle = bootstrap3_inline
|
heartbeat = 1
|
||||||
separator =
|
|
||||||
|
[google]
|
||||||
|
analytics_id =
|
||||||
+5
-12
File diff suppressed because one or more lines are too long
+26
-3
@@ -1,3 +1,11 @@
|
|||||||
|
ul.navbar-nav.navbar-right {
|
||||||
|
position: absolute;
|
||||||
|
right: 100px;
|
||||||
|
}
|
||||||
|
label, th {
|
||||||
|
font-weigth: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
div.w2p_flash {
|
div.w2p_flash {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -108,7 +116,6 @@ select.autocomplete {
|
|||||||
background: url(../images/background.jpg) no-repeat center center;
|
background: url(../images/background.jpg) no-repeat center center;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
padding-top: 60px;
|
|
||||||
margin-bottom: 60px;
|
margin-bottom: 60px;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
@@ -126,8 +133,7 @@ html {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background: #333;
|
background: #f7f7f7;
|
||||||
color: #aaa;
|
|
||||||
}
|
}
|
||||||
header h1 {
|
header h1 {
|
||||||
color: #FFF!important;
|
color: #FFF!important;
|
||||||
@@ -315,3 +321,20 @@ td.w2p_fc,
|
|||||||
input[type=checkbox], input[type=radio] {
|
input[type=checkbox], input[type=radio] {
|
||||||
margin: 4px 4px 0 0;
|
margin: 4px 4px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* for backward compatbility with pre-font-awesome */
|
||||||
|
.icon.plus,.icon.arrowleft,.icon.download,.icon.trash,.icon.pen,.icon.arrowright,.icon.magnifier {
|
||||||
|
display: inline-block;
|
||||||
|
font: normal normal normal 14px/1 FontAwesome;
|
||||||
|
font-size: inherit;
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
.icon.plus:before { content: "\f067";}
|
||||||
|
.icon.arrowleft:before { content: "\f060";}
|
||||||
|
.icon.download:before { content: "\f019";}
|
||||||
|
.icon.trash:before { content: "\f1f8";}
|
||||||
|
.icon.pen:before { content: "\f040";}
|
||||||
|
.icon.arrowright:before { content: "\f061";}
|
||||||
|
.icon.magnifier:before { content: "\f002";}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 724 KiB |
+5
-5
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
|||||||
/*! Respond.js v1.4.2: min/max-width media query polyfill
|
|
||||||
* Copyright 2014 Scott Jehl
|
|
||||||
* Licensed under MIT
|
|
||||||
* http://j.mp/respondjs */
|
|
||||||
|
|
||||||
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b<t.length;b++){var c=t[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!p[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(w(c.styleSheet.rawCssText,e,f),p[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!s||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}x()};y(),c.update=y,c.getEmValue=u,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
|
|
||||||
Created and copyrighted by Massimo Di Pierro <massimo.dipierro@gmail.com>
|
|
||||||
(MIT license)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
<script src="share.js"></script>
|
|
||||||
|
|
||||||
**/
|
|
||||||
|
|
||||||
jQuery(function(){
|
|
||||||
var script_source = jQuery('script[src*="share.js"]').attr('src');
|
|
||||||
var params = function(name,default_value) {
|
|
||||||
var match = RegExp('[?&]' + name + '=([^&]*)').exec(script_source);
|
|
||||||
return match && decodeURIComponent(match[1].replace(/\+/g, ' '))||default_value;
|
|
||||||
}
|
|
||||||
var path = params('static','social');
|
|
||||||
var url = encodeURIComponent(window.location.href);
|
|
||||||
var host = window.location.hostname;
|
|
||||||
var title = escape(jQuery('title').text());
|
|
||||||
var twit = 'http://twitter.com/home?status='+title+'%20'+url;
|
|
||||||
var facebook = 'http://www.facebook.com/sharer.php?u='+url;
|
|
||||||
var gplus = 'https://plus.google.com/share?url='+url;
|
|
||||||
var tbar = '<div id="socialdrawer"><span>Share<br/></span><div id="sicons"><a href="'+twit+'" id="twit" title="Share on twitter"><img src="'+path+'/twitter.png" alt="Share on Twitter" width="32" height="32" /></a><a href="'+facebook+'" id="facebook" title="Share on Facebook"><img src="'+path+'/facebook.png" alt="Share on facebook" width="32" height="32" /></a><a href="'+gplus+'" id="gplus" title="Share on Google Plus"><img src="'+path+'/gplus-32.png" alt="Share on Google Plus" width="32" height="32" /></a></div></div>';
|
|
||||||
// Add the share tool bar.
|
|
||||||
jQuery('body').append(tbar);
|
|
||||||
var st = jQuery('#socialdrawer');
|
|
||||||
st.css({'opacity':'.7','z-index':'3000','background':'#FFF','border':'solid 1px #666','border-width':' 1px 0 0 1px','height':'20px','width':'40px','position':'fixed','bottom':'0','right':'0','padding':'2px 5px','overflow':'hidden','-webkit-border-top-left-radius':' 12px','-moz-border-radius-topleft':' 12px','border-top-left-radius':' 12px','-moz-box-shadow':' -3px -3px 3px rgba(0,0,0,0.5)','-webkit-box-shadow':' -3px -3px 3px rgba(0,0,0,0.5)','box-shadow':' -3px -3px 3px rgba(0,0,0,0.5)'});
|
|
||||||
jQuery('#socialdrawer a').css({'float':'left','width':'32px','margin':'3px 2px 2px 2px','padding':'0','cursor':'pointer'});
|
|
||||||
jQuery('#socialdrawer span').css({'float':'left','margin':'2px 3px','text-shadow':' 1px 1px 1px #FFF','color':'#444','font-size':'12px','line-height':'1em'});
|
|
||||||
jQuery('#socialdrawer img').hide();
|
|
||||||
// hover
|
|
||||||
st.click(function(){
|
|
||||||
jQuery(this).animate({height:'40px', width:'160px', opacity: 0.95}, 300);
|
|
||||||
jQuery('#socialdrawer img').show();
|
|
||||||
});
|
|
||||||
//leave
|
|
||||||
st.mouseleave(function(){
|
|
||||||
st.animate({height:'20px', width: '40px', opacity: .7}, 300);
|
|
||||||
jQuery('#socialdrawer img').hide();
|
|
||||||
return false;
|
|
||||||
} );
|
|
||||||
});
|
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
$.error('web2py.js has already been loaded!');
|
$.error('web2py.js has already been loaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||||
|
var animateIn = 'fadeIn';
|
||||||
|
// var animateIn = 'slideDown';
|
||||||
|
|
||||||
String.prototype.reverse = function () {
|
String.prototype.reverse = function () {
|
||||||
return this.split('').reverse().join('');
|
return this.split('').reverse().join('');
|
||||||
};
|
};
|
||||||
@@ -178,7 +182,7 @@
|
|||||||
},
|
},
|
||||||
/* manage errors in forms */
|
/* manage errors in forms */
|
||||||
manage_errors: function (target) {
|
manage_errors: function (target) {
|
||||||
$('div.error', target).hide().slideDown('slow');
|
$('div.error', target).hide()[animateIn]('slow');
|
||||||
},
|
},
|
||||||
after_ajax: function (xhr) {
|
after_ajax: function (xhr) {
|
||||||
/* called whenever an ajax request completes */
|
/* called whenever an ajax request completes */
|
||||||
@@ -263,13 +267,17 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
/* help preventing double form submission for normal form (not LOADed) */
|
/* help preventing double form submission for normal form (not LOADed) */
|
||||||
$(doc).on('submit', 'form', function () {
|
$(doc).on('submit', 'form', function (e) {
|
||||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||||
web2py.disableElement(submit_button);
|
submit_buttons.each(function() {
|
||||||
|
web2py.disableElement($(this));
|
||||||
|
})
|
||||||
/* safeguard in case the form doesn't trigger a refresh,
|
/* safeguard in case the form doesn't trigger a refresh,
|
||||||
see https://github.com/web2py/web2py/issues/1100 */
|
see https://github.com/web2py/web2py/issues/1100 */
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
web2py.enableElement(submit_button);
|
submit_buttons.each(function() {
|
||||||
|
web2py.enableElement($(this));
|
||||||
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
doc.ajaxSuccess(function (e, xhr) {
|
doc.ajaxSuccess(function (e, xhr) {
|
||||||
@@ -320,7 +328,15 @@
|
|||||||
form.submit(function (e) {
|
form.submit(function (e) {
|
||||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
|
||||||
|
var formData;
|
||||||
|
if (FORMDATA_IS_SUPPORTED) {
|
||||||
|
formData = new FormData(form[0]); // Allows file uploads.
|
||||||
|
} else {
|
||||||
|
formData = form.serialize(); // Fallback for older browsers.
|
||||||
|
}
|
||||||
|
web2py.ajax_page('post', url, formData, target, form);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||||
@@ -339,11 +355,18 @@
|
|||||||
if (web2py.isUndefined(element)) element = $(document);
|
if (web2py.isUndefined(element)) element = $(document);
|
||||||
/* if target is not there, fill it with something that there isn't in the page*/
|
/* if target is not there, fill it with something that there isn't in the page*/
|
||||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||||
|
|
||||||
|
/* processData and contentType must be set to false when passing a FormData
|
||||||
|
object to jQuery.ajax. */
|
||||||
|
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||||
|
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||||
$.ajax({
|
$.ajax({
|
||||||
'type': method,
|
'type': method,
|
||||||
'url': action,
|
'url': action,
|
||||||
'data': data,
|
'data': data,
|
||||||
|
'processData': !isFormData,
|
||||||
|
'contentType': contentType,
|
||||||
'beforeSend': function (xhr, settings) {
|
'beforeSend': function (xhr, settings) {
|
||||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||||
xhr.setRequestHeader('web2py-component-element', target);
|
xhr.setRequestHeader('web2py-component-element', target);
|
||||||
@@ -594,8 +617,8 @@
|
|||||||
flash: function (message, status) {
|
flash: function (message, status) {
|
||||||
var flash = $('.w2p_flash');
|
var flash = $('.w2p_flash');
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
flash.html(message).addClass(status);
|
flash.text(message).addClass(status);
|
||||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
if (flash.html()) flash.append('<span id="closeflash"> × </span>')[animateIn]();
|
||||||
},
|
},
|
||||||
hide_flash: function () {
|
hide_flash: function () {
|
||||||
$('.w2p_flash').fadeOut(0).html('');
|
$('.w2p_flash').fadeOut(0).html('');
|
||||||
@@ -609,7 +632,7 @@
|
|||||||
for (var k = 0; k < triggers[id].length; k++) {
|
for (var k = 0; k < triggers[id].length; k++) {
|
||||||
var dep = $('#' + triggers[id][k], target);
|
var dep = $('#' + triggers[id][k], target);
|
||||||
var tr = $('#' + triggers[id][k] + '__row', target);
|
var tr = $('#' + triggers[id][k] + '__row', target);
|
||||||
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
|
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
|
||||||
else tr.hide();
|
else tr.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -699,8 +722,9 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Disables form elements:
|
/* Disables form elements:
|
||||||
|
- Does not disable elements with 'data-w2p_disable' attribute
|
||||||
- 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
|
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||||
- Sets disabled property to true
|
- Sets disabled property to true
|
||||||
*/
|
*/
|
||||||
disableFormElements: function (form) {
|
disableFormElements: function (form) {
|
||||||
@@ -712,13 +736,15 @@
|
|||||||
if (!web2py.isUndefined(disable)) {
|
if (!web2py.isUndefined(disable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(disable_with)) {
|
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||||
element.data('w2p_disable_with', element[method]());
|
if (web2py.isUndefined(disable_with)) {
|
||||||
|
element.data('w2p_disable_with', element[method]());
|
||||||
|
}
|
||||||
|
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||||
|
element.data('w2p_enable_with', element[method]());
|
||||||
|
}
|
||||||
|
element[method](element.data('w2p_disable_with'));
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
|
||||||
element.data('w2p_enable_with', element[method]());
|
|
||||||
}
|
|
||||||
element[method](element.data('w2p_disable_with'));
|
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -799,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
|||||||
web2py_trap_link = jQuery.web2py.trap_link;
|
web2py_trap_link = jQuery.web2py.trap_link;
|
||||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||||
*/
|
*/
|
||||||
/* compatibility code - end*/
|
/* compatibility code - end*/
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
{{=T.M("(**%.0d MB**)", total['bytes'] / 1048576)}}
|
{{=T.M("(**%.0d MB**)", total['bytes'] / 1048576)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{=T.M("**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
|
{{=T.M("**not available** (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
{{=T.M("(**%.0d MB**)", ram['bytes'] / 10485576)}}
|
{{=T.M("(**%.0d MB**)", ram['bytes'] / 10485576)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{=T.M("``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
|
{{=T.M("``**not available**``:red (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
{{=T.M("(**%.0d MB**)", disk['bytes'] / 1048576)}}
|
{{=T.M("(**%.0d MB**)", disk['bytes'] / 1048576)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{=T.M("``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
|
{{=T.M("``**not available**``:red (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
{{=T("No databases in this application")}}
|
{{=T("No databases in this application")}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<div id="vis"></div>
|
<div id="vis"></div>
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/d3_graph.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||||
<script>
|
<script>
|
||||||
// Define the d3 input data
|
// Define the d3 input data
|
||||||
{{from gluon.serializers import json }}
|
{{from gluon.serializers import json }}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
{{left_sidebar_enabled,right_sidebar_enabled=False,('message' in globals())}}
|
|
||||||
{{extend 'layout.html'}}
|
{{extend 'layout.html'}}
|
||||||
|
|
||||||
{{block header}}
|
{{block header}}
|
||||||
<header class="container-fluid background">
|
<center style="background-color: #333; color:white; padding:30px">
|
||||||
<div class="jumbotron text-center">
|
<h1>/{{=request.application}}/{{=request.controller}}/{{=request.function}}
|
||||||
{{if response.title:}}
|
</center>
|
||||||
<h1>{{=response.title}}
|
|
||||||
<small>{{=response.subtitle or ''}}</small></h1>
|
|
||||||
{{pass}}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if 'message' in globals():}}
|
{{if 'message' in globals():}}
|
||||||
@@ -27,26 +21,23 @@
|
|||||||
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li>
|
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li>
|
||||||
<li>{{=T('You can modify this application and adapt it to your needs')}}</li>
|
<li>{{=T('You can modify this application and adapt it to your needs')}}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
<center style="padding:50px">
|
||||||
|
<a class="btn btn-primary" href="{{=URL('admin','default','index')}}">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
{{=T("admin")}}
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-secondary" href="{{=URL('examples','default','index')}}">{{=T("Online examples")}}</a>
|
||||||
|
<a class="btn btn-secondary" href="http://web2py.com">web2py.com</a>
|
||||||
|
<a class="btn btn-secondary" href="http://web2py.com/book">{{=T('Documentation')}}</a>
|
||||||
|
<a class="btn btn-secondary" href="{{=URL('default','api_get_user_email')}}">{{=T('API Example')}}</a>
|
||||||
|
<a class="btn btn-secondary" href="{{=URL('default','grid/auth_user')}}">{{=T('Grid Example')}}</a>
|
||||||
|
<a class="btn btn-secondary" href="{{=URL('default','wiki')}}">{{=T('Wiki Example')}}</a>
|
||||||
|
</center>
|
||||||
{{elif 'content' in globals():}}
|
{{elif 'content' in globals():}}
|
||||||
{{=content}}
|
{{=content}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{=BEAUTIFY(response._vars)}}
|
{{=BEAUTIFY(response._vars)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{block right_sidebar}}
|
|
||||||
<div class="panel panel-info">
|
|
||||||
<div class="panel-heading"><h3 class="panel-title"><a class="btn-block"
|
|
||||||
href="{{=URL('admin','default','index')}}">
|
|
||||||
<i class="glyphicon glyphicon-cog"></i>
|
|
||||||
{{=T("admin")}}
|
|
||||||
</a></h3></div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{=T("Don't know what to do?")}}
|
|
||||||
</div>
|
|
||||||
<ul class="list-group">
|
|
||||||
<li class="list-group-item">{{=A(T("Online examples"), _href=URL('examples','default','index'))}}</li>
|
|
||||||
<li class="list-group-item"><a href="http://web2py.com">web2py.com</a></li>
|
|
||||||
<li class="list-group-item"><a href="http://web2py.com/book">{{=T('Documentation')}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
{{
|
{{
|
||||||
if request.args(0)=='login':
|
if request.args(0)=='login':
|
||||||
if not 'register' in auth.settings.actions_disabled:
|
if not 'register' in auth.settings.actions_disabled:
|
||||||
form.add_button(T('Sign Up'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn btn-default')
|
form.add_button(T('Sign Up'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn btn-default btn-secondary')
|
||||||
pass
|
pass
|
||||||
if not 'request_reset_password' in auth.settings.actions_disabled:
|
if not 'request_reset_password' in auth.settings.actions_disabled:
|
||||||
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn btn-default')
|
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn btn-default btn-secondary')
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
=form
|
=form
|
||||||
|
|||||||
@@ -11,6 +11,3 @@ It is used as default when a view is not provided for your controllers
|
|||||||
{{elif len(response._vars)>1:}}
|
{{elif len(response._vars)>1:}}
|
||||||
{{=BEAUTIFY(response._vars)}}
|
{{=BEAUTIFY(response._vars)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{if request.is_local:}}
|
|
||||||
{{=response.toolbar()}}
|
|
||||||
{{pass}}
|
|
||||||
|
|||||||
@@ -20,85 +20,83 @@
|
|||||||
http://google.com/webmasters -->
|
http://google.com/webmasters -->
|
||||||
<meta name="google-site-verification" content="">
|
<meta name="google-site-verification" content="">
|
||||||
<!-- include stylesheets -->
|
<!-- include stylesheets -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap4.css')}}"/>
|
||||||
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
|
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
|
||||||
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
|
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
|
||||||
<!-- All JavaScript at the bottom, except for Modernizr which enables
|
<!-- All JavaScript at the bottom, except for Modernizr which enables
|
||||||
HTML5 elements & feature detects -->
|
HTML5 elements & feature detects -->
|
||||||
<script src="{{=URL('static','js/modernizr-2.8.3.min.js')}}"></script>
|
<script src="{{=URL('static','js/modernizr-2.8.3.min.js')}}"></script>
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src="{{=URL('static','js/respond-1.4.2.min.js')}}"></script>
|
|
||||||
<![endif]-->
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
{{include 'web2py_ajax.html'}} <!-- this includes jquery.js, calendar.js/.css and web2py.js -->
|
{{include 'web2py_ajax.html'}} <!-- this includes jquery.js, calendar.js/.css and web2py.js -->
|
||||||
{{block head}}{{end}}
|
{{block head}}{{end}}
|
||||||
{{
|
|
||||||
# using sidebars need to know what sidebar you want to use
|
|
||||||
mc0 = 'col-md-12'
|
|
||||||
mc1 = 'col-md-9'
|
|
||||||
mc2 = 'col-md-6'
|
|
||||||
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
|
|
||||||
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
|
|
||||||
middle_column = {0: mc0, 1: mc1, 2: mc2}[
|
|
||||||
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
|
|
||||||
}}
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
|
|
||||||
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
|
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
|
||||||
<!-- Navbar ======================================= -->
|
<!-- Navbar ======================================= -->
|
||||||
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
<nav class="navbar navbar-toggleable-md navbar-light bg-faded">
|
||||||
<div class="container-fluid">
|
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<div class="navbar-header">
|
<span class="navbar-toggler-icon"></span>
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
</button>
|
||||||
<span class="sr-only">Toggle navigation</span>
|
<a class="navbar-brand" href="http://web2py.com">web2py</a>
|
||||||
<span class="icon-bar"></span>
|
<div id="navbarNavDropdown" class="collapse navbar-collapse">
|
||||||
<span class="icon-bar"></span>
|
<ul class="navbar-nav">
|
||||||
<span class="icon-bar"></span>
|
{{for _item in response.menu or []:}}
|
||||||
</button>
|
{{if len(_item)<4 or not _item[3]:}}
|
||||||
{{=response.logo or ''}}
|
<li class="nav-item {{if _item[1]:}}active{{pass}}">
|
||||||
</div>
|
<a class="nav-link" href="{{=_item[2]}}">{{=_item[0]}}</a>
|
||||||
<div class="collapse navbar-collapse">
|
</li>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
{{else:}}
|
||||||
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}}
|
<li class="nav-item dropdown">
|
||||||
</ul>
|
<a class="nav-link dropdown-toggle" href="{{=_item[2]}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{=_item[0]}}</a>
|
||||||
{{if response.menu:}}
|
<div class="dropdown-menu">
|
||||||
{{=MENU(response.menu, _class='nav navbar-nav',li_class='dropdown',ul_class='dropdown-menu')}}
|
{{for _subitem in _item[3]:}}
|
||||||
|
<a class="dropdown-item" href="{{=_subitem[2]}}">{{=_subitem[0]}}</a>
|
||||||
|
{{pass}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
</div>
|
{{pass}}
|
||||||
|
</ul>
|
||||||
|
<form class="form-inline my-2 my-lg-0">
|
||||||
|
<input class="form-control mr-sm-2" type="text" placeholder="Search">
|
||||||
|
</form>
|
||||||
|
{{if 'auth' in globals():}}
|
||||||
|
<ul class="navbar-nav navbar-right">
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
{{if auth.user:}}{{=auth.user.first_name}}{{else:}}LOGIN{{pass}}
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
{{if auth.user:}}
|
||||||
|
<a class="dropdown-item" href="{{=URL('default','user/profile')}}">{{=T('Profile')}}</a>
|
||||||
|
<a class="dropdown-item" href="{{=URL('default','user/change_password')}}">{{=T('Change Password')}}</a>
|
||||||
|
<a class="dropdown-item" href="{{=URL('default','user/logout')}}">{{=T('Logout')}}</a>
|
||||||
|
{{else:}}
|
||||||
|
<a class="dropdown-item" href="{{=URL('default','user/login')}}">{{=T('Login')}}</a>
|
||||||
|
<a class="dropdown-item" href="{{=URL('default','user/register')}}">{{=T('Sign up')}}</a>
|
||||||
|
<a class="dropdown-item" href="{{=URL('default','user/retrieve_password')}}">{{=T('Lost Password')}}</a>
|
||||||
|
{{pass}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{pass}}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Masthead ===================================== -->
|
<!-- Masthead ===================================== -->
|
||||||
{{block header}}
|
{{block header}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<!-- Main ========================================= -->
|
<!-- Main ========================================= -->
|
||||||
<!-- Begin page content -->
|
<!-- Begin page content -->
|
||||||
<div class="container-fluid main-container">
|
<div class="container-fluid main-container">
|
||||||
{{if left_sidebar_enabled:}}
|
<div class="col-md-12">
|
||||||
<div class="col-md-3 left-sidebar">
|
|
||||||
{{block left_sidebar}}
|
|
||||||
<h3>Left Sidebar</h3>
|
|
||||||
<p></p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{pass}}
|
|
||||||
|
|
||||||
<div class="{{=middle_column}}">
|
|
||||||
{{block center}}
|
{{block center}}
|
||||||
{{include}}
|
{{include}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{=response.toolbar() if response.show_toolbar else ''}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if right_sidebar_enabled:}}
|
|
||||||
<div class="col-md-3">
|
|
||||||
{{block right_sidebar}}
|
|
||||||
<h3>Right Sidebar</h3>
|
|
||||||
<p></p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{pass}}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{block footer}} <!-- this is default footer -->
|
{{block footer}} <!-- this is default footer -->
|
||||||
@@ -114,7 +112,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
<!-- The javascript =============================== -->
|
<!-- The javascript =============================== -->
|
||||||
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
|
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
|
||||||
<script src="{{=URL('static','js/web2py-bootstrap3.js')}}"></script>
|
<script src="{{=URL('static','js/web2py-bootstrap4.js')}}"></script>
|
||||||
{{block page_js}}{{end page_js}}
|
{{block page_js}}{{end page_js}}
|
||||||
{{if response.google_analytics_id:}}
|
{{if response.google_analytics_id:}}
|
||||||
<!-- Analytics ==================================== -->
|
<!-- Analytics ==================================== -->
|
||||||
@@ -125,7 +123,5 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
<!-- Share ============================y============ -->
|
|
||||||
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-1
@@ -10,6 +10,10 @@ environment:
|
|||||||
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
|
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
|
||||||
PYTHON_ARCH: "64"
|
PYTHON_ARCH: "64"
|
||||||
|
|
||||||
|
- PYTHON: "C:/Python36"
|
||||||
|
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
|
||||||
|
PYTHON_ARCH: "64"
|
||||||
|
|
||||||
clone_depth: 50
|
clone_depth: 50
|
||||||
|
|
||||||
init:
|
init:
|
||||||
@@ -20,7 +24,6 @@ install:
|
|||||||
- python -m ensurepip
|
- python -m ensurepip
|
||||||
- pip install codecov
|
- pip install codecov
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
- pip install pycrypto
|
|
||||||
# Check that we have the expected version and architecture for Python
|
# Check that we have the expected version and architecture for Python
|
||||||
- "python --version"
|
- "python --version"
|
||||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||||
|
|||||||
+7
-5
@@ -26,7 +26,7 @@ if PY2:
|
|||||||
from email.MIMEText import MIMEText
|
from email.MIMEText import MIMEText
|
||||||
from email.Charset import add_charset, QP as charset_QP
|
from email.Charset import add_charset, QP as charset_QP
|
||||||
from urllib import FancyURLopener, urlencode, urlopen
|
from urllib import FancyURLopener, urlencode, urlopen
|
||||||
from urllib import quote as urllib_quote, unquote as urllib_unquote
|
from urllib import quote as urllib_quote, unquote as urllib_unquote, quote_plus as urllib_quote_plus
|
||||||
from string import maketrans
|
from string import maketrans
|
||||||
from types import ClassType
|
from types import ClassType
|
||||||
import cgi
|
import cgi
|
||||||
@@ -35,6 +35,7 @@ if PY2:
|
|||||||
from gluon.contrib import ipaddress
|
from gluon.contrib import ipaddress
|
||||||
BytesIO = StringIO
|
BytesIO = StringIO
|
||||||
reduce = reduce
|
reduce = reduce
|
||||||
|
reload = reload
|
||||||
hashlib_md5 = hashlib.md5
|
hashlib_md5 = hashlib.md5
|
||||||
iterkeys = lambda d: d.iterkeys()
|
iterkeys = lambda d: d.iterkeys()
|
||||||
itervalues = lambda d: d.itervalues()
|
itervalues = lambda d: d.itervalues()
|
||||||
@@ -63,7 +64,7 @@ if PY2:
|
|||||||
return None
|
return None
|
||||||
if isinstance(obj, (bytes, bytearray, buffer)):
|
if isinstance(obj, (bytes, bytearray, buffer)):
|
||||||
return bytes(obj)
|
return bytes(obj)
|
||||||
if isinstance(obj, unicode):
|
if hasattr(obj, 'encode'):
|
||||||
return obj.encode(charset, errors)
|
return obj.encode(charset, errors)
|
||||||
raise TypeError('Expected bytes')
|
raise TypeError('Expected bytes')
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ else:
|
|||||||
import pickle
|
import pickle
|
||||||
from io import StringIO, BytesIO
|
from io import StringIO, BytesIO
|
||||||
import copyreg
|
import copyreg
|
||||||
|
from importlib import reload
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from http import cookies as Cookie
|
from http import cookies as Cookie
|
||||||
@@ -94,7 +96,7 @@ else:
|
|||||||
from email.header import Header
|
from email.header import Header
|
||||||
from email.charset import Charset, add_charset, QP as charset_QP
|
from email.charset import Charset, add_charset, QP as charset_QP
|
||||||
from urllib.request import FancyURLopener, urlopen
|
from urllib.request import FancyURLopener, urlopen
|
||||||
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode
|
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode, quote_plus as urllib_quote_plus
|
||||||
from http import cookiejar as cookielib
|
from http import cookiejar as cookielib
|
||||||
from xmlrpc.client import ProtocolError
|
from xmlrpc.client import ProtocolError
|
||||||
import html # warning, this is the python3 module and not the web2py html module
|
import html # warning, this is the python3 module and not the web2py html module
|
||||||
@@ -122,7 +124,7 @@ else:
|
|||||||
return None
|
return None
|
||||||
if isinstance(obj, (bytes, bytearray, memoryview)):
|
if isinstance(obj, (bytes, bytearray, memoryview)):
|
||||||
return bytes(obj)
|
return bytes(obj)
|
||||||
if isinstance(obj, str):
|
if hasattr(obj, 'encode'):
|
||||||
return obj.encode(charset, errors)
|
return obj.encode(charset, errors)
|
||||||
raise TypeError('Expected bytes')
|
raise TypeError('Expected bytes')
|
||||||
|
|
||||||
@@ -151,7 +153,7 @@ def with_metaclass(meta, *bases):
|
|||||||
def to_unicode(obj, charset='utf-8', errors='strict'):
|
def to_unicode(obj, charset='utf-8', errors='strict'):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return None
|
return None
|
||||||
if not isinstance(obj, bytes):
|
if not hasattr(obj, 'decode'):
|
||||||
return text_type(obj)
|
return text_type(obj)
|
||||||
return obj.decode(charset, errors)
|
return obj.decode(charset, errors)
|
||||||
|
|
||||||
|
|||||||
+15
-10
@@ -54,7 +54,8 @@ def app_pack(app, request, raise_ex=False, filenames=None):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if filenames is None: app_cleanup(app, request)
|
if filenames is None:
|
||||||
|
app_cleanup(app, request)
|
||||||
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
|
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
|
||||||
w2p_pack(filename, apath(app, request), filenames=filenames)
|
w2p_pack(filename, apath(app, request), filenames=filenames)
|
||||||
return filename
|
return filename
|
||||||
@@ -104,7 +105,8 @@ def app_cleanup(app, request):
|
|||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': os.unlink(os.path.join(path, f))
|
if f[:1] != '.':
|
||||||
|
os.unlink(os.path.join(path, f))
|
||||||
except IOError:
|
except IOError:
|
||||||
r = False
|
r = False
|
||||||
|
|
||||||
@@ -113,7 +115,8 @@ def app_cleanup(app, request):
|
|||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
if f[:1] != '.':
|
||||||
|
recursive_unlink(os.path.join(path, f))
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
r = False
|
r = False
|
||||||
|
|
||||||
@@ -123,7 +126,8 @@ def app_cleanup(app, request):
|
|||||||
CacheOnDisk(folder=path).clear()
|
CacheOnDisk(folder=path).clear()
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
if f[:1] != '.':
|
||||||
|
recursive_unlink(os.path.join(path, f))
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
r = False
|
r = False
|
||||||
return r
|
return r
|
||||||
@@ -175,10 +179,9 @@ def app_create(app, request, force=False, key=None, info=False):
|
|||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
w2p_unpack('welcome.w2p', path)
|
w2p_unpack('welcome.w2p', path)
|
||||||
for subfolder in [
|
for subfolder in ['models', 'views', 'controllers', 'databases',
|
||||||
'models', 'views', 'controllers', 'databases',
|
'modules', 'cron', 'errors', 'sessions', 'cache',
|
||||||
'modules', 'cron', 'errors', 'sessions', 'cache',
|
'languages', 'static', 'private', 'uploads']:
|
||||||
'languages', 'static', 'private', 'uploads']:
|
|
||||||
subpath = os.path.join(path, subfolder)
|
subpath = os.path.join(path, subfolder)
|
||||||
if not os.path.exists(subpath):
|
if not os.path.exists(subpath):
|
||||||
os.mkdir(subpath)
|
os.mkdir(subpath)
|
||||||
@@ -368,7 +371,7 @@ def unzip(filename, dir, subfolder=''):
|
|||||||
for name in sorted(zf.namelist()):
|
for name in sorted(zf.namelist()):
|
||||||
if not name.startswith(subfolder):
|
if not name.startswith(subfolder):
|
||||||
continue
|
continue
|
||||||
#print name[n:]
|
# print name[n:]
|
||||||
if name.endswith('/'):
|
if name.endswith('/'):
|
||||||
folder = os.path.join(dir, name[n:])
|
folder = os.path.join(dir, name[n:])
|
||||||
if not os.path.exists(folder):
|
if not os.path.exists(folder):
|
||||||
@@ -435,6 +438,7 @@ def add_path_first(path):
|
|||||||
if not global_settings.web2py_runtime_gae:
|
if not global_settings.web2py_runtime_gae:
|
||||||
site.addsitedir(path)
|
site.addsitedir(path)
|
||||||
|
|
||||||
|
|
||||||
def try_mkdir(path):
|
def try_mkdir(path):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
@@ -444,11 +448,12 @@ def try_mkdir(path):
|
|||||||
else:
|
else:
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.strerror == 'File exists': # In case of race condition.
|
if e.strerror == 'File exists': # In case of race condition.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def create_missing_folders():
|
def create_missing_folders():
|
||||||
if not global_settings.web2py_runtime_gae:
|
if not global_settings.web2py_runtime_gae:
|
||||||
for path in ('applications', 'deposit', 'site-packages', 'logs'):
|
for path in ('applications', 'deposit', 'site-packages', 'logs'):
|
||||||
|
|||||||
+36
-19
@@ -20,10 +20,10 @@ DEFAULT = lambda: None
|
|||||||
class AuthAPI(object):
|
class AuthAPI(object):
|
||||||
"""
|
"""
|
||||||
AuthAPI is a barebones Auth implementation which does not have a concept of
|
AuthAPI is a barebones Auth implementation which does not have a concept of
|
||||||
HTML forms or redirects, emailing or even an URL, you are responsible for
|
HTML forms or redirects, emailing or even an URL, you are responsible for
|
||||||
all that if you use it.
|
all that if you use it.
|
||||||
The main Auth functions such as login, logout, register, profile are designed
|
The main Auth functions such as login, logout, register, profile are designed
|
||||||
in a Dict In -> Dict Out logic so, for instance, if you set
|
in a Dict In -> Dict Out logic so, for instance, if you set
|
||||||
registration_requires_verification you are responsible for sending the key to
|
registration_requires_verification you are responsible for sending the key to
|
||||||
the user and even rolling back the transaction if you can't do it.
|
the user and even rolling back the transaction if you can't do it.
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ class AuthAPI(object):
|
|||||||
if auth.last_visit and auth.last_visit + delta > now:
|
if auth.last_visit and auth.last_visit + delta > now:
|
||||||
self.user = auth.user
|
self.user = auth.user
|
||||||
# this is a trick to speed up sessions to avoid many writes
|
# this is a trick to speed up sessions to avoid many writes
|
||||||
if (now - auth.last_visit).seconds > (auth.expiration / 10):
|
if (now - auth.last_visit).seconds > (auth.expiration // 10):
|
||||||
auth.last_visit = now
|
auth.last_visit = now
|
||||||
else:
|
else:
|
||||||
self.user = None
|
self.user = None
|
||||||
@@ -245,13 +245,13 @@ class AuthAPI(object):
|
|||||||
migrate = db._migrate
|
migrate = db._migrate
|
||||||
if fake_migrate is None:
|
if fake_migrate is None:
|
||||||
fake_migrate = db._fake_migrate
|
fake_migrate = db._fake_migrate
|
||||||
|
|
||||||
settings = self.settings
|
settings = self.settings
|
||||||
if username is None:
|
if username is None:
|
||||||
username = settings.use_username
|
username = settings.use_username
|
||||||
else:
|
else:
|
||||||
settings.use_username = username
|
settings.use_username = username
|
||||||
|
|
||||||
if not self.signature:
|
if not self.signature:
|
||||||
self.define_signature()
|
self.define_signature()
|
||||||
if signature is True:
|
if signature is True:
|
||||||
@@ -557,27 +557,43 @@ class AuthAPI(object):
|
|||||||
self.log_event(self.messages['del_membership_log'],
|
self.log_event(self.messages['del_membership_log'],
|
||||||
dict(user_id=user_id, group_id=group_id))
|
dict(user_id=user_id, group_id=group_id))
|
||||||
ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete()
|
ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete()
|
||||||
if group_id in self.user_groups:
|
if group_id in self.user_groups and user_id == self.user_id:
|
||||||
del self.user_groups[group_id]
|
del self.user_groups[group_id]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def has_membership(self, group_id=None, user_id=None, role=None):
|
def has_membership(self, group_id=None, user_id=None, role=None, cached=False):
|
||||||
"""
|
"""
|
||||||
Checks if user is member of group_id or role
|
Checks if user is member of group_id or role
|
||||||
|
|
||||||
|
NOTE: To avoid database query at each page load that use auth.has_membership, someone can use cached=True.
|
||||||
|
If cached is set to True has_membership() check group_id or role only against auth.user_groups variable
|
||||||
|
which is populated properly only at login time. This means that if an user membership change during a
|
||||||
|
given session the user has to log off and log in again in order to auth.user_groups to be properly
|
||||||
|
recreated and reflecting the user membership modification. There is one exception to this log off and
|
||||||
|
log in process which is in case that the user change his own membership, in this case auth.user_groups
|
||||||
|
can be properly update for the actual connected user because web2py has access to the proper session
|
||||||
|
user_groups variable. To make use of this exception someone has to place an "auth.update_groups()"
|
||||||
|
instruction in his app code to force auth.user_groups to be updated. As mention this will only work if it
|
||||||
|
the user itself that change it membership not if another user, let say an administrator, change someone
|
||||||
|
else's membership.
|
||||||
"""
|
"""
|
||||||
group_id = group_id or self.id_group(role)
|
|
||||||
try:
|
|
||||||
group_id = int(group_id)
|
|
||||||
except:
|
|
||||||
group_id = self.id_group(group_id) # interpret group_id as a role
|
|
||||||
if not user_id and self.user:
|
if not user_id and self.user:
|
||||||
user_id = self.user.id
|
user_id = self.user.id
|
||||||
membership = self.table_membership()
|
if cached:
|
||||||
if group_id and user_id and self.db((membership.user_id == user_id) &
|
id_role = group_id or role
|
||||||
(membership.group_id == group_id)).select():
|
r = (user_id and id_role in self.user_groups.values()) or (user_id and id_role in self.user_groups)
|
||||||
r = True
|
|
||||||
else:
|
else:
|
||||||
r = False
|
group_id = group_id or self.id_group(role)
|
||||||
|
try:
|
||||||
|
group_id = int(group_id)
|
||||||
|
except:
|
||||||
|
group_id = self.id_group(group_id) # interpret group_id as a role
|
||||||
|
membership = self.table_membership()
|
||||||
|
if group_id and user_id and self.db((membership.user_id == user_id) &
|
||||||
|
(membership.group_id == group_id)).select():
|
||||||
|
r = True
|
||||||
|
else:
|
||||||
|
r = False
|
||||||
self.log_event(self.messages['has_membership_log'],
|
self.log_event(self.messages['has_membership_log'],
|
||||||
dict(user_id=user_id, group_id=group_id, check=r))
|
dict(user_id=user_id, group_id=group_id, check=r))
|
||||||
return r
|
return r
|
||||||
@@ -972,7 +988,8 @@ class AuthAPI(object):
|
|||||||
requires = [requires]
|
requires = [requires]
|
||||||
requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
|
requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
|
||||||
if requires:
|
if requires:
|
||||||
requires[0].min_length = 0
|
requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
|
||||||
|
requires[0].min_length = 0 # But do not enforce minimum length for the old password
|
||||||
|
|
||||||
old_password = kwargs.get('old_password', '')
|
old_password = kwargs.get('old_password', '')
|
||||||
new_password = kwargs.get('new_password', '')
|
new_password = kwargs.get('new_password', '')
|
||||||
@@ -1012,7 +1029,7 @@ class AuthAPI(object):
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Verify a given registration_key actually exists in the user table.
|
Verify a given registration_key actually exists in the user table.
|
||||||
Resets the key to empty string '' or 'pending' if
|
Resets the key to empty string '' or 'pending' if
|
||||||
setttings.registration_requires_approval is true.
|
setttings.registration_requires_approval is true.
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
|
|||||||
+57
-42
@@ -18,7 +18,7 @@ import fnmatch
|
|||||||
import os
|
import os
|
||||||
import copy
|
import copy
|
||||||
import random
|
import random
|
||||||
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long
|
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long, reload
|
||||||
from gluon.storage import Storage, List
|
from gluon.storage import Storage, List
|
||||||
from gluon.template import parse_template
|
from gluon.template import parse_template
|
||||||
from gluon.restricted import restricted, compile2
|
from gluon.restricted import restricted, compile2
|
||||||
@@ -41,11 +41,12 @@ import imp
|
|||||||
import logging
|
import logging
|
||||||
import types
|
import types
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
logger = logging.getLogger("web2py")
|
|
||||||
from gluon import rewrite
|
from gluon import rewrite
|
||||||
from gluon.custom_import import custom_import_install
|
from gluon.custom_import import custom_import_install
|
||||||
import py_compile
|
import py_compile
|
||||||
|
|
||||||
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
is_pypy = settings.global_settings.is_pypy
|
is_pypy = settings.global_settings.is_pypy
|
||||||
is_gae = settings.global_settings.web2py_runtime_gae
|
is_gae = settings.global_settings.web2py_runtime_gae
|
||||||
is_jython = settings.global_settings.is_jython
|
is_jython = settings.global_settings.is_jython
|
||||||
@@ -111,7 +112,7 @@ class mybuiltin(object):
|
|||||||
NOTE could simple use a dict and populate it,
|
NOTE could simple use a dict and populate it,
|
||||||
NOTE not sure if this changes things though if monkey patching import.....
|
NOTE not sure if this changes things though if monkey patching import.....
|
||||||
"""
|
"""
|
||||||
#__builtins__
|
# __builtins__
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return getattr(builtin, key)
|
return getattr(builtin, key)
|
||||||
@@ -185,7 +186,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
else:
|
else:
|
||||||
statement = "$.web2py.component('%s','%s');" % (url, target)
|
statement = "$.web2py.component('%s','%s');" % (url, target)
|
||||||
attr['_data-w2p_remote'] = url
|
attr['_data-w2p_remote'] = url
|
||||||
if not target is None:
|
if target is not None:
|
||||||
return DIV(content, **attr)
|
return DIV(content, **attr)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -204,14 +205,15 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
other_response = Response()
|
other_response = Response()
|
||||||
other_request.env.path_info = '/' + \
|
other_request.env.path_info = '/' + \
|
||||||
'/'.join([request.application, c, f] +
|
'/'.join([request.application, c, f] +
|
||||||
map(str, other_request.args))
|
[str(a) for a in other_request.args])
|
||||||
other_request.env.query_string = \
|
other_request.env.query_string = \
|
||||||
vars and URL(vars=vars).split('?')[1] or ''
|
vars and URL(vars=vars).split('?')[1] or ''
|
||||||
other_request.env.http_web2py_component_location = \
|
other_request.env.http_web2py_component_location = \
|
||||||
request.env.path_info
|
request.env.path_info
|
||||||
other_request.cid = target
|
other_request.cid = target
|
||||||
other_request.env.http_web2py_component_element = target
|
other_request.env.http_web2py_component_element = target
|
||||||
other_request.restful = types.MethodType(request.restful.__func__, other_request) # A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
other_request.restful = types.MethodType(request.restful.__func__, other_request)
|
||||||
|
# A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
||||||
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
||||||
|
|
||||||
other_environment = copy.copy(current.globalenv) # NASTY
|
other_environment = copy.copy(current.globalenv) # NASTY
|
||||||
@@ -286,7 +288,7 @@ class LoadFactory(object):
|
|||||||
other_response = globals.Response()
|
other_response = globals.Response()
|
||||||
other_request.env.path_info = '/' + \
|
other_request.env.path_info = '/' + \
|
||||||
'/'.join([request.application, c, f] +
|
'/'.join([request.application, c, f] +
|
||||||
map(str, other_request.args))
|
[str(a) for a in other_request.args])
|
||||||
other_request.env.query_string = \
|
other_request.env.query_string = \
|
||||||
vars and html.URL(vars=vars).split('?')[1] or ''
|
vars and html.URL(vars=vars).split('?')[1] or ''
|
||||||
other_request.env.http_web2py_component_location = \
|
other_request.env.http_web2py_component_location = \
|
||||||
@@ -405,7 +407,7 @@ def build_environment(request, response, session, store_current=True):
|
|||||||
"""
|
"""
|
||||||
Build the environment dictionary into which web2py files are executed.
|
Build the environment dictionary into which web2py files are executed.
|
||||||
"""
|
"""
|
||||||
#h,v = html,validators
|
# h,v = html,validators
|
||||||
environment = dict(_base_environment_)
|
environment = dict(_base_environment_)
|
||||||
|
|
||||||
if not request.env:
|
if not request.env:
|
||||||
@@ -418,7 +420,7 @@ def build_environment(request, response, session, store_current=True):
|
|||||||
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
|
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
|
||||||
]
|
]
|
||||||
|
|
||||||
t = environment['T'] = translator(os.path.join(request.folder,'languages'),
|
t = environment['T'] = translator(os.path.join(request.folder, 'languages'),
|
||||||
request.env.http_accept_language)
|
request.env.http_accept_language)
|
||||||
c = environment['cache'] = Cache(request)
|
c = environment['cache'] = Cache(request)
|
||||||
|
|
||||||
@@ -506,10 +508,12 @@ def compile_models(folder):
|
|||||||
save_pyc(filename)
|
save_pyc(filename)
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def find_exposed_functions(data):
|
def find_exposed_functions(data):
|
||||||
data = regex_longcomments.sub('',data)
|
data = regex_longcomments.sub('', data)
|
||||||
return regex_expose.findall(data)
|
return regex_expose.findall(data)
|
||||||
|
|
||||||
|
|
||||||
def compile_controllers(folder):
|
def compile_controllers(folder):
|
||||||
"""
|
"""
|
||||||
Compiles all the controllers in the application specified by `folder`
|
Compiles all the controllers in the application specified by `folder`
|
||||||
@@ -524,16 +528,19 @@ def compile_controllers(folder):
|
|||||||
command = data + "\nresponse._vars=response._caller(%s)\n" % \
|
command = data + "\nresponse._vars=response._caller(%s)\n" % \
|
||||||
function
|
function
|
||||||
filename = pjoin(folder, 'compiled',
|
filename = pjoin(folder, 'compiled',
|
||||||
'controllers.%s.%s.py' % (fname[:-3],function))
|
'controllers.%s.%s.py' % (fname[:-3], function))
|
||||||
write_file(filename, command)
|
write_file(filename, command)
|
||||||
save_pyc(filename)
|
save_pyc(filename)
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def model_cmp(a, b, sep='.'):
|
def model_cmp(a, b, sep='.'):
|
||||||
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
|
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
|
||||||
|
|
||||||
|
|
||||||
def model_cmp_sep(a, b, sep=os.path.sep):
|
def model_cmp_sep(a, b, sep=os.path.sep):
|
||||||
return model_cmp(a,b,sep)
|
return model_cmp(a, b, sep)
|
||||||
|
|
||||||
|
|
||||||
def run_models_in(environment):
|
def run_models_in(environment):
|
||||||
"""
|
"""
|
||||||
@@ -544,7 +551,7 @@ def run_models_in(environment):
|
|||||||
request = current.request
|
request = current.request
|
||||||
folder = request.folder
|
folder = request.folder
|
||||||
c = request.controller
|
c = request.controller
|
||||||
#f = environment['request'].function
|
# f = environment['request'].function
|
||||||
response = current.response
|
response = current.response
|
||||||
|
|
||||||
path = pjoin(folder, 'models')
|
path = pjoin(folder, 'models')
|
||||||
@@ -557,9 +564,11 @@ def run_models_in(environment):
|
|||||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
|
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
|
||||||
else:
|
else:
|
||||||
if compiled:
|
if compiled:
|
||||||
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), key=lambda f: '{0:03d}'.format(f.count('.')) + f)
|
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0),
|
||||||
|
key=lambda f: '{0:03d}'.format(f.count('.')) + f)
|
||||||
else:
|
else:
|
||||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
|
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False),
|
||||||
|
key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
|
||||||
|
|
||||||
models_to_run = None
|
models_to_run = None
|
||||||
for model in models:
|
for model in models:
|
||||||
@@ -570,10 +579,10 @@ def run_models_in(environment):
|
|||||||
if models_to_run:
|
if models_to_run:
|
||||||
if compiled:
|
if compiled:
|
||||||
n = len(cpath)+8
|
n = len(cpath)+8
|
||||||
fname = model[n:-4].replace('.','/')+'.py'
|
fname = model[n:-4].replace('.', '/')+'.py'
|
||||||
else:
|
else:
|
||||||
n = len(path)+1
|
n = len(path)+1
|
||||||
fname = model[n:].replace(os.path.sep,'/')
|
fname = model[n:].replace(os.path.sep, '/')
|
||||||
if not regex.search(fname) and c != 'appadmin':
|
if not regex.search(fname) and c != 'appadmin':
|
||||||
continue
|
continue
|
||||||
elif compiled:
|
elif compiled:
|
||||||
@@ -583,6 +592,7 @@ def run_models_in(environment):
|
|||||||
ccode = getcfs(model, model, f)
|
ccode = getcfs(model, model, f)
|
||||||
restricted(ccode, environment, layer=model)
|
restricted(ccode, environment, layer=model)
|
||||||
|
|
||||||
|
|
||||||
def run_controller_in(controller, function, environment):
|
def run_controller_in(controller, function, environment):
|
||||||
"""
|
"""
|
||||||
Runs the controller.function() (for the app specified by
|
Runs the controller.function() (for the app specified by
|
||||||
@@ -596,13 +606,13 @@ def run_controller_in(controller, function, environment):
|
|||||||
badc = 'invalid controller (%s/%s)' % (controller, function)
|
badc = 'invalid controller (%s/%s)' % (controller, function)
|
||||||
badf = 'invalid function (%s/%s)' % (controller, function)
|
badf = 'invalid function (%s/%s)' % (controller, function)
|
||||||
if os.path.exists(cpath):
|
if os.path.exists(cpath):
|
||||||
filename = pjoin(cpath, 'controllers.%s.%s.pyc'
|
filename = pjoin(cpath, 'controllers.%s.%s.pyc' % (controller, function))
|
||||||
% (controller, function))
|
try:
|
||||||
if not os.path.exists(filename):
|
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
|
||||||
|
except IOError:
|
||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||||
web2py_error=badf)
|
web2py_error=badf)
|
||||||
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
|
|
||||||
elif function == '_TEST':
|
elif function == '_TEST':
|
||||||
# TESTING: adjust the path to include site packages
|
# TESTING: adjust the path to include site packages
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
@@ -623,15 +633,15 @@ def run_controller_in(controller, function, environment):
|
|||||||
code += TEST_CODE
|
code += TEST_CODE
|
||||||
ccode = compile2(code, filename)
|
ccode = compile2(code, filename)
|
||||||
else:
|
else:
|
||||||
filename = pjoin(folder, 'controllers/%s.py'
|
filename = pjoin(folder, 'controllers/%s.py' % controller)
|
||||||
% controller)
|
try:
|
||||||
if not os.path.exists(filename):
|
code = getcfs(filename, filename, lambda: read_file(filename))
|
||||||
|
except IOError:
|
||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
||||||
web2py_error=badc)
|
web2py_error=badc)
|
||||||
code = getcfs(filename, filename, lambda: read_file(filename))
|
|
||||||
exposed = find_exposed_functions(code)
|
exposed = find_exposed_functions(code)
|
||||||
if not function in exposed:
|
if function not in exposed:
|
||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||||
web2py_error=badf)
|
web2py_error=badf)
|
||||||
@@ -666,8 +676,9 @@ def run_view_in(environment):
|
|||||||
badv = 'invalid view (%s)' % view
|
badv = 'invalid view (%s)' % view
|
||||||
patterns = response.get('generic_patterns')
|
patterns = response.get('generic_patterns')
|
||||||
layer = None
|
layer = None
|
||||||
|
scode = None
|
||||||
if patterns:
|
if patterns:
|
||||||
regex = re_compile('|'.join(map(fnmatch.translate, patterns)))
|
regex = re_compile('|'.join(fnmatch.translate(p) for p in patterns))
|
||||||
short_action = '%(controller)s/%(function)s.%(extension)s' % request
|
short_action = '%(controller)s/%(function)s.%(extension)s' % request
|
||||||
allow_generic = regex.search(short_action)
|
allow_generic = regex.search(short_action)
|
||||||
else:
|
else:
|
||||||
@@ -678,7 +689,7 @@ def run_view_in(environment):
|
|||||||
layer = 'file stream'
|
layer = 'file stream'
|
||||||
else:
|
else:
|
||||||
filename = pjoin(folder, 'views', view)
|
filename = pjoin(folder, 'views', view)
|
||||||
if os.path.exists(cpath): # compiled views
|
if os.path.exists(cpath): # compiled views
|
||||||
x = view.replace('/', '.')
|
x = view.replace('/', '.')
|
||||||
files = ['views.%s.pyc' % x]
|
files = ['views.%s.pyc' % x]
|
||||||
is_compiled = os.path.exists(pjoin(cpath, files[0]))
|
is_compiled = os.path.exists(pjoin(cpath, files[0]))
|
||||||
@@ -698,23 +709,27 @@ def run_view_in(environment):
|
|||||||
ccode = getcfs(compiled, compiled, lambda: read_pyc(compiled))
|
ccode = getcfs(compiled, compiled, lambda: read_pyc(compiled))
|
||||||
layer = compiled
|
layer = compiled
|
||||||
break
|
break
|
||||||
if not os.path.exists(filename) and allow_generic:
|
# if the view is not compiled
|
||||||
view = 'generic.' + request.extension
|
if not layer:
|
||||||
filename = pjoin(folder, 'views', view)
|
if not os.path.exists(filename) and allow_generic:
|
||||||
if not os.path.exists(filename):
|
view = 'generic.' + request.extension
|
||||||
raise HTTP(404,
|
filename = pjoin(folder, 'views', view)
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
if not os.path.exists(filename):
|
||||||
web2py_error=badv)
|
raise HTTP(404,
|
||||||
layer = filename
|
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
||||||
# Compile the template
|
web2py_error=badv)
|
||||||
ccode = parse_template(view,
|
# Parse template
|
||||||
pjoin(folder, 'views'),
|
scode = parse_template(view,
|
||||||
context=environment)
|
pjoin(folder, 'views'),
|
||||||
|
context=environment)
|
||||||
restricted(ccode, environment, layer=layer)
|
# Compile template
|
||||||
|
ccode = compile2(scode, filename)
|
||||||
|
layer = filename
|
||||||
|
restricted(ccode, environment, layer=layer, scode=scode)
|
||||||
# parse_template saves everything in response body
|
# parse_template saves everything in response body
|
||||||
return environment['response'].body.getvalue()
|
return environment['response'].body.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def remove_compiled_application(folder):
|
def remove_compiled_application(folder):
|
||||||
"""
|
"""
|
||||||
Deletes the folder `compiled` containing the compiled application.
|
Deletes the folder `compiled` containing the compiled application.
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ CONTENT_TYPE = {
|
|||||||
'.lha': 'application/x-lha',
|
'.lha': 'application/x-lha',
|
||||||
'.lhs': 'text/x-literate-haskell',
|
'.lhs': 'text/x-literate-haskell',
|
||||||
'.lhz': 'application/x-lhz',
|
'.lhz': 'application/x-lhz',
|
||||||
'.load' : 'text/html',
|
'.load': 'text/html',
|
||||||
'.log': 'text/x-log',
|
'.log': 'text/x-log',
|
||||||
'.lrz': 'application/x-lrzip',
|
'.lrz': 'application/x-lrzip',
|
||||||
'.ltx': 'text/x-tex',
|
'.ltx': 'text/x-tex',
|
||||||
@@ -823,7 +823,7 @@ CONTENT_TYPE = {
|
|||||||
'.xsd': 'application/xml',
|
'.xsd': 'application/xml',
|
||||||
'.xsl': 'application/xslt+xml',
|
'.xsl': 'application/xslt+xml',
|
||||||
'.xslfo': 'text/x-xslfo',
|
'.xslfo': 'text/x-xslfo',
|
||||||
'.xslm' : 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
'.xslm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||||
'.xslt': 'application/xslt+xml',
|
'.xslt': 'application/xslt+xml',
|
||||||
'.xspf': 'application/xspf+xml',
|
'.xspf': 'application/xspf+xml',
|
||||||
'.xul': 'application/vnd.mozilla.xul+xml',
|
'.xul': 'application/vnd.mozilla.xul+xml',
|
||||||
@@ -843,7 +843,7 @@ def contenttype(filename, default='text/plain'):
|
|||||||
"""
|
"""
|
||||||
Returns the Content-Type string matching extension of the given filename.
|
Returns the Content-Type string matching extension of the given filename.
|
||||||
"""
|
"""
|
||||||
filename=to_native(filename)
|
filename = to_native(filename)
|
||||||
i = filename.rfind('.')
|
i = filename.rfind('.')
|
||||||
if i >= 0:
|
if i >= 0:
|
||||||
default = CONTENT_TYPE.get(filename[i:].lower(), default)
|
default = CONTENT_TYPE.get(filename[i:].lower(), default)
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ db = get_db()
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from gluon import *
|
from gluon import *
|
||||||
from pydal.adapters import ADAPTERS, PostgreSQLAdapter
|
from pydal.adapters import adapters, PostgrePsyco
|
||||||
from pydal.helpers.classes import UseDatabaseStoredFile
|
from pydal.helpers.classes import DatabaseStoredFile
|
||||||
|
|
||||||
class HerokuPostgresAdapter(UseDatabaseStoredFile,PostgreSQLAdapter):
|
@adapters.register_for('postgres')
|
||||||
drivers = ('psycopg2',)
|
class HerokuPostgresAdapter(DatabaseStoredFile, PostgrePsyco):
|
||||||
uploads_in_blob = True
|
uploads_in_blob = True
|
||||||
|
|
||||||
ADAPTERS['postgres'] = HerokuPostgresAdapter
|
|
||||||
|
|
||||||
def get_db(name = None, pool_size=10):
|
def get_db(name = None, pool_size=10):
|
||||||
if not name:
|
if not name:
|
||||||
names = [n for n in os.environ.keys()
|
names = [n for n in os.environ.keys()
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class RESIZE(object):
|
|||||||
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
|
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
|
||||||
background.paste(
|
background.paste(
|
||||||
img,
|
img,
|
||||||
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
|
((self.nx - img.size[0]) // 2, (self.ny - img.size[1]) // 2))
|
||||||
background.save(s, 'JPEG', quality=self.quality)
|
background.save(s, 'JPEG', quality=self.quality)
|
||||||
else:
|
else:
|
||||||
img.save(s, 'JPEG', quality=self.quality)
|
img.save(s, 'JPEG', quality=self.quality)
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ class CasAuth(object):
|
|||||||
email=lambda v: v.get('email', None),
|
email=lambda v: v.get('email', None),
|
||||||
user_id=lambda v: v['user']),
|
user_id=lambda v: v['user']),
|
||||||
casversion=1,
|
casversion=1,
|
||||||
casusername='cas:user'
|
casusername='cas:user',
|
||||||
|
change_password_url=None
|
||||||
):
|
):
|
||||||
self.urlbase = urlbase
|
self.urlbase = urlbase
|
||||||
self.cas_login_url = "%s/%s" % (self.urlbase, actions[0])
|
self.cas_login_url = "%s/%s" % (self.urlbase, actions[0])
|
||||||
@@ -64,6 +65,9 @@ class CasAuth(object):
|
|||||||
#vars=current.request.vars,
|
#vars=current.request.vars,
|
||||||
scheme=True)
|
scheme=True)
|
||||||
|
|
||||||
|
# URL to let users change their password in the IDP system
|
||||||
|
self.cas_change_password_url = change_password_url
|
||||||
|
|
||||||
def login_url(self, next="/"):
|
def login_url(self, next="/"):
|
||||||
current.session.token = self._CAS_login()
|
current.session.token = self._CAS_login()
|
||||||
return next
|
return next
|
||||||
@@ -74,6 +78,10 @@ class CasAuth(object):
|
|||||||
self._CAS_logout()
|
self._CAS_logout()
|
||||||
return next
|
return next
|
||||||
|
|
||||||
|
def change_password_url(self, next="/"):
|
||||||
|
self._CAS_change_password()
|
||||||
|
return next
|
||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
user = current.session.token
|
user = current.session.token
|
||||||
if user:
|
if user:
|
||||||
@@ -135,3 +143,6 @@ class CasAuth(object):
|
|||||||
redirects to the CAS logout page
|
redirects to the CAS logout page
|
||||||
"""
|
"""
|
||||||
redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url))
|
redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url))
|
||||||
|
|
||||||
|
def _CAS_change_password(self):
|
||||||
|
redirect(self.cas_change_password_url)
|
||||||
|
|||||||
@@ -78,10 +78,13 @@ class RPXAccount(object):
|
|||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
request = self.request
|
request = self.request
|
||||||
if request.vars.token:
|
# Janrain now sends the token via both a POST body and the query
|
||||||
|
# string, so we should keep only one of these.
|
||||||
|
token = request.post_vars.token or request.get_vars.token
|
||||||
|
if token:
|
||||||
user = Storage()
|
user = Storage()
|
||||||
data = urllib.urlencode(
|
data = urllib.urlencode(
|
||||||
dict(apiKey=self.api_key, token=request.vars.token))
|
dict(apiKey=self.api_key, token=token))
|
||||||
auth_info_json = fetch(self.auth_url + '?' + data)
|
auth_info_json = fetch(self.auth_url + '?' + data)
|
||||||
auth_info = json.loads(auth_info_json)
|
auth_info = json.loads(auth_info_json)
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,11 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import urllib
|
|
||||||
from gluon import *
|
from gluon import *
|
||||||
from gluon.tools import fetch
|
from gluon.tools import fetch
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
import json
|
import json
|
||||||
|
from gluon._compat import urlencode
|
||||||
|
|
||||||
class RPXAccount(object):
|
class RPXAccount(object):
|
||||||
|
|
||||||
@@ -78,10 +77,13 @@ class RPXAccount(object):
|
|||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
request = self.request
|
request = self.request
|
||||||
if request.vars.token:
|
# Janrain now sends the token via both a POST body and the query
|
||||||
|
# string, so we should keep only one of these.
|
||||||
|
token = request.post_vars.token or request.get_vars.token
|
||||||
|
if token:
|
||||||
user = Storage()
|
user = Storage()
|
||||||
data = urllib.urlencode(
|
data = urlencode(
|
||||||
dict(apiKey=self.api_key, token=request.vars.token))
|
dict(apiKey=self.api_key, token=token))
|
||||||
auth_info_json = fetch(self.auth_url + '?' + data)
|
auth_info_json = fetch(self.auth_url + '?' + data)
|
||||||
auth_info = json.loads(auth_info_json)
|
auth_info = json.loads(auth_info_json)
|
||||||
|
|
||||||
|
|||||||
@@ -104,11 +104,12 @@ def obj2dict(obj, processed=None):
|
|||||||
types.BuiltinFunctionType,
|
types.BuiltinFunctionType,
|
||||||
types.BuiltinMethodType))
|
types.BuiltinMethodType))
|
||||||
|
|
||||||
def saml2_handler(session, request, config_filename = None):
|
def saml2_handler(session, request, config_filename = None, entityid = None):
|
||||||
config_filename = config_filename or os.path.join(request.folder,'private','sp_conf')
|
config_filename = config_filename or os.path.join(request.folder,'private','sp_conf')
|
||||||
client = Saml2Client(config_file = config_filename)
|
client = Saml2Client(config_file = config_filename)
|
||||||
idps = client.metadata.with_descriptor("idpsso")
|
if not entityid:
|
||||||
entityid = idps.keys()[0]
|
idps = client.metadata.with_descriptor("idpsso")
|
||||||
|
entityid = idps.keys()[0]
|
||||||
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
|
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
|
||||||
binding, destination = client.pick_binding(
|
binding, destination = client.pick_binding(
|
||||||
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
|
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
|
||||||
@@ -119,7 +120,7 @@ def saml2_handler(session, request, config_filename = None):
|
|||||||
if not request.vars.SAMLResponse:
|
if not request.vars.SAMLResponse:
|
||||||
req_id, req = client.create_authn_request(destination, binding=binding)
|
req_id, req = client.create_authn_request(destination, binding=binding)
|
||||||
relay_state = web2py_uuid().replace('-','')
|
relay_state = web2py_uuid().replace('-','')
|
||||||
session.saml_outstanding_queries = {req_id: request.url}
|
session.saml_outstanding_queries = {req_id: request.url}
|
||||||
session.saml_req_id = req_id
|
session.saml_req_id = req_id
|
||||||
http_args = client.apply_binding(binding, str(req), destination,
|
http_args = client.apply_binding(binding, str(req), destination,
|
||||||
relay_state=relay_state)
|
relay_state=relay_state)
|
||||||
@@ -145,12 +146,21 @@ class Saml2Auth(object):
|
|||||||
username=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
username=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
||||||
email=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
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],
|
user_id=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
||||||
)):
|
), logout_url=None, change_password_url=None, entityid=None):
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
self.maps = maps
|
self.maps = maps
|
||||||
|
|
||||||
|
# URL for redirecting users to when they sign out
|
||||||
|
self.saml_logout_url = logout_url
|
||||||
|
|
||||||
|
# URL to let users change their password in the IDP system
|
||||||
|
self.saml_change_password_url = change_password_url
|
||||||
|
|
||||||
|
# URL to specify an IDP if using federation metadata or an MDQ
|
||||||
|
self.entityid = entityid
|
||||||
|
|
||||||
def login_url(self, next="/"):
|
def login_url(self, next="/"):
|
||||||
d = saml2_handler(current.session, current.request)
|
d = saml2_handler(current.session, current.request, entityid=self.entityid)
|
||||||
if 'url' in d:
|
if 'url' in d:
|
||||||
redirect(d['url'])
|
redirect(d['url'])
|
||||||
elif 'error' in d:
|
elif 'error' in d:
|
||||||
@@ -170,6 +180,12 @@ class Saml2Auth(object):
|
|||||||
|
|
||||||
def logout_url(self, next="/"):
|
def logout_url(self, next="/"):
|
||||||
current.session.saml2_info = None
|
current.session.saml2_info = None
|
||||||
|
current.session.auth = None
|
||||||
|
self._SAML_logout()
|
||||||
|
return next
|
||||||
|
|
||||||
|
def change_password_url(self, next="/"):
|
||||||
|
self._SAML_change_password()
|
||||||
return next
|
return next
|
||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
@@ -180,3 +196,13 @@ class Saml2Auth(object):
|
|||||||
d[key] = self.maps[key](user)
|
d[key] = self.maps[key](user)
|
||||||
return d
|
return d
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _SAML_logout(self):
|
||||||
|
"""
|
||||||
|
exposed SAML.logout()
|
||||||
|
redirects to the SAML logout page
|
||||||
|
"""
|
||||||
|
redirect(self.saml_logout_url)
|
||||||
|
|
||||||
|
def _SAML_change_password(self):
|
||||||
|
redirect(self.saml_change_password_url)
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ Original author: Zachary Voase
|
|||||||
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
|
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
from StringIO import StringIO # The pure-Python StringIO supports unicode.
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,30 +8,40 @@ Created by: Ross Peoples <ross.peoples@gmail.com>
|
|||||||
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
|
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import cssmin
|
from . import cssmin
|
||||||
import jsmin
|
from . import jsmin
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
hashlib_md5 = hashlib.md5
|
||||||
|
else:
|
||||||
|
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
|
||||||
|
|
||||||
|
def open_py23(filename, mode):
|
||||||
|
if PY2:
|
||||||
|
f = open(filename, mode + 'b')
|
||||||
|
else:
|
||||||
|
f = open(filename, mode, encoding="utf8")
|
||||||
|
return f
|
||||||
|
|
||||||
def read_binary_file(filename):
|
def read_binary_file(filename):
|
||||||
f = open(filename, 'rb')
|
f = open_py23(filename, 'r')
|
||||||
data = f.read()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def write_binary_file(filename, data):
|
def write_binary_file(filename, data):
|
||||||
f = open(filename, 'wb')
|
f = open_py23(filename, 'w')
|
||||||
f.write(data)
|
f.write(data)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def fix_links(css, static_path):
|
def fix_links(css, static_path):
|
||||||
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
|
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
|
||||||
|
|
||||||
|
|
||||||
def minify(files, path_info, folder, optimize_css, optimize_js,
|
def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||||
ignore_concat=[],
|
ignore_concat=[],
|
||||||
ignore_minify=['/jquery.js', '/anytime.js']):
|
ignore_minify=['/jquery.js', '/anytime.js']):
|
||||||
@@ -109,7 +119,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
|||||||
js.append(contents)
|
js.append(contents)
|
||||||
else:
|
else:
|
||||||
js.append(filename)
|
js.append(filename)
|
||||||
dest_key = hashlib.md5(repr(processed)).hexdigest()
|
dest_key = hashlib_md5(repr(processed)).hexdigest()
|
||||||
if css and concat_css:
|
if css and concat_css:
|
||||||
css = '\n\n'.join(contents for contents in css)
|
css = '\n\n'.join(contents for contents in css)
|
||||||
if not inline_css:
|
if not inline_css:
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ def populate_generator(table, default=True, compute=False, contents={}):
|
|||||||
continue
|
continue
|
||||||
elif field.type == 'upload':
|
elif field.type == 'upload':
|
||||||
continue
|
continue
|
||||||
|
elif field.compute is not None:
|
||||||
|
continue
|
||||||
elif default and not field.default in (None, ''):
|
elif default and not field.default in (None, ''):
|
||||||
record[fieldname] = field.default
|
record[fieldname] = field.default
|
||||||
elif compute and field.compute:
|
elif compute and field.compute:
|
||||||
|
|||||||
@@ -673,3 +673,23 @@ def simple_detect(agent):
|
|||||||
if os_version:
|
if os_version:
|
||||||
os = " ".join((os, os_version))
|
os = " ".join((os, os_version))
|
||||||
return os, browser
|
return os, browser
|
||||||
|
|
||||||
|
|
||||||
|
class mobilize(object):
|
||||||
|
"""
|
||||||
|
Decorator for controller functions so they use different views for mobile devices.
|
||||||
|
|
||||||
|
WARNING: If you update httpagentparser make sure to leave mobilize for
|
||||||
|
backwards compatibility.
|
||||||
|
"""
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
from gluon import current
|
||||||
|
user_agent = current.request.user_agent()
|
||||||
|
if user_agent.is_mobile:
|
||||||
|
items = current.response.view.split('.')
|
||||||
|
items.insert(-1, 'mobile')
|
||||||
|
current.response.view = '.'.join(items)
|
||||||
|
return self.func()
|
||||||
|
|||||||
@@ -94,32 +94,10 @@ import optparse
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import gluon.utils
|
import gluon.utils
|
||||||
|
from gluon._compat import to_native, to_bytes, urlencode, urlopen
|
||||||
if (sys.version_info[0] == 2):
|
|
||||||
from urllib import urlencode, urlopen
|
|
||||||
def to_bytes(obj, charset='utf-8', errors='strict'):
|
|
||||||
if obj is None:
|
|
||||||
return None
|
|
||||||
if isinstance(obj, (bytes, bytearray, buffer)):
|
|
||||||
return bytes(obj)
|
|
||||||
if isinstance(obj, unicode):
|
|
||||||
return obj.encode(charset, errors)
|
|
||||||
raise TypeError('Expected bytes')
|
|
||||||
else:
|
|
||||||
from urllib.request import urlopen
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
def to_bytes(obj, charset='utf-8', errors='strict'):
|
|
||||||
if obj is None:
|
|
||||||
return None
|
|
||||||
if isinstance(obj, (bytes, bytearray, memoryview)):
|
|
||||||
return bytes(obj)
|
|
||||||
if isinstance(obj, str):
|
|
||||||
return obj.encode(charset, errors)
|
|
||||||
raise TypeError('Expected bytes')
|
|
||||||
|
|
||||||
listeners, names, tokens = {}, {}, {}
|
listeners, names, tokens = {}, {}, {}
|
||||||
|
|
||||||
|
|
||||||
def websocket_send(url, message, hmac_key=None, group='default'):
|
def websocket_send(url, message, hmac_key=None, group='default'):
|
||||||
sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
|
sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
|
||||||
params = urlencode(
|
params = urlencode(
|
||||||
@@ -138,8 +116,8 @@ class PostHandler(tornado.web.RequestHandler):
|
|||||||
if hmac_key and not 'signature' in self.request.arguments:
|
if hmac_key and not 'signature' in self.request.arguments:
|
||||||
self.send_error(401)
|
self.send_error(401)
|
||||||
if 'message' in self.request.arguments:
|
if 'message' in self.request.arguments:
|
||||||
message = self.request.arguments['message'][0]
|
message = self.request.arguments['message'][0].decode(encoding='UTF-8')
|
||||||
group = self.request.arguments.get('group', ['default'])[0]
|
group = self.request.arguments.get('group', ['default'])[0].decode(encoding='UTF-8')
|
||||||
print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
|
print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
|
||||||
if hmac_key:
|
if hmac_key:
|
||||||
signature = self.request.arguments['signature'][0]
|
signature = self.request.arguments['signature'][0]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
Support for smart import syntax for web2py applications
|
Support for smart import syntax for web2py applications
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
"""
|
"""
|
||||||
from gluon._compat import builtin, unicodeT, PY2, to_native
|
from gluon._compat import builtin, unicodeT, PY2, to_native, reload
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|||||||
+62
-37
@@ -14,6 +14,12 @@ from pydal import DAL as DAL
|
|||||||
from pydal import Field
|
from pydal import Field
|
||||||
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
||||||
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||||
|
from pydal.migrator import Migrator, InDBMigrator
|
||||||
|
from gluon.serializers import custom_json, xml
|
||||||
|
from gluon.utils import web2py_uuid
|
||||||
|
from gluon import sqlhtml
|
||||||
|
from pydal.drivers import DRIVERS
|
||||||
|
|
||||||
|
|
||||||
def _default_validators(db, field):
|
def _default_validators(db, field):
|
||||||
"""
|
"""
|
||||||
@@ -26,7 +32,11 @@ def _default_validators(db, field):
|
|||||||
field_type, field_length = field.type, field.length
|
field_type, field_length = field.type, field.length
|
||||||
requires = []
|
requires = []
|
||||||
|
|
||||||
if field_type in (('string', 'text', 'password')):
|
if isinstance(field.options, list) and field.requires:
|
||||||
|
requires = validators.IS_IN_SET(field.options, multiple=field_type.startswith('list:'))
|
||||||
|
elif field.regex and not field.requires:
|
||||||
|
requires.append(validators.IS_REGEX(regex))
|
||||||
|
elif field_type in (('string', 'text', 'password')):
|
||||||
requires.append(validators.IS_LENGTH(field_length))
|
requires.append(validators.IS_LENGTH(field_length))
|
||||||
elif field_type == 'json':
|
elif field_type == 'json':
|
||||||
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
|
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
|
||||||
@@ -44,48 +54,64 @@ def _default_validators(db, field):
|
|||||||
requires.append(validators.IS_TIME())
|
requires.append(validators.IS_TIME())
|
||||||
elif field_type == 'datetime':
|
elif field_type == 'datetime':
|
||||||
requires.append(validators.IS_DATETIME())
|
requires.append(validators.IS_DATETIME())
|
||||||
elif db and field_type.startswith('reference') and \
|
elif db and field_type.startswith('reference'):
|
||||||
field_type.find('.') < 0 and \
|
if field_type.find('.') < 0 and field_type[10:] in db.tables:
|
||||||
field_type[10:] in db.tables:
|
referenced = db[field_type[10:]]
|
||||||
referenced = db[field_type[10:]]
|
if hasattr(referenced, '_format') and referenced._format:
|
||||||
if hasattr(referenced, '_format') and referenced._format:
|
requires = validators.IS_IN_DB(db, referenced._id,referenced._format)
|
||||||
requires = validators.IS_IN_DB(db, referenced._id,
|
else:
|
||||||
referenced._format)
|
requires = validators.IS_IN_DB(db, referenced._id)
|
||||||
if field.unique:
|
elif field_type.find('.') > 0 and field_type[10:].split('.')[0] in db.tables:
|
||||||
requires._and = validators.IS_NOT_IN_DB(db, field)
|
table_field = field_type[10:].split('.')
|
||||||
if field.tablename == field_type[10:]:
|
table_name=table_field[0]
|
||||||
return validators.IS_EMPTY_OR(requires)
|
field_name=table_field[1]
|
||||||
return requires
|
referenced = db[table_name]
|
||||||
elif db and field_type.startswith('list:reference') and \
|
if hasattr(referenced, '_format') and referenced._format:
|
||||||
field_type.find('.') < 0 and \
|
requires = validators.IS_IN_DB(db, referenced[field_name],referenced._format)
|
||||||
field_type[15:] in db.tables:
|
else:
|
||||||
referenced = db[field_type[15:]]
|
requires = validators.IS_IN_DB(db, referenced[field_name])
|
||||||
if hasattr(referenced, '_format') and referenced._format:
|
|
||||||
requires = validators.IS_IN_DB(db, referenced._id,
|
|
||||||
referenced._format, multiple=True)
|
|
||||||
else:
|
|
||||||
requires = validators.IS_IN_DB(db, referenced._id,
|
|
||||||
multiple=True)
|
|
||||||
if field.unique:
|
if field.unique:
|
||||||
requires._and = validators.IS_NOT_IN_DB(db, field)
|
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||||
if not field.notnull:
|
if not field.notnull:
|
||||||
requires = validators.IS_EMPTY_OR(requires)
|
requires = validators.IS_EMPTY_OR(requires)
|
||||||
return requires
|
return requires
|
||||||
|
elif db and field_type.startswith('list:reference'):
|
||||||
|
if field_type.find('.') < 0 and field_type[15:] in db.tables:
|
||||||
|
referenced = db[field_type[15:]]
|
||||||
|
if hasattr(referenced, '_format') and referenced._format:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced._id,
|
||||||
|
referenced._format, multiple=True)
|
||||||
|
else:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced._id,
|
||||||
|
multiple=True)
|
||||||
|
elif field_type.find('.') > 0 and field_type[15:].split('.')[0] in db.tables:
|
||||||
|
table_field = field_type[15:].split('.')
|
||||||
|
table_name=table_field[0]
|
||||||
|
field_name=table_field[1]
|
||||||
|
referenced = db[table_name]
|
||||||
|
if hasattr(referenced, '_format') and referenced._format:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced[field_name],
|
||||||
|
referenced._format, multiple=True)
|
||||||
|
else:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced[field_name],
|
||||||
|
multiple=True)
|
||||||
|
if field.unique:
|
||||||
|
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||||
|
if not field.notnull:
|
||||||
|
requires = validators.IS_EMPTY_OR(requires)
|
||||||
|
return requires
|
||||||
# does not get here for reference and list:reference
|
# does not get here for reference and list:reference
|
||||||
if field.unique:
|
if isinstance(requires, list):
|
||||||
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
|
if field.unique:
|
||||||
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
|
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
|
||||||
if (field.notnull or field.unique) and field_type not in excluded_fields:
|
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
|
||||||
requires.insert(0, validators.IS_NOT_EMPTY())
|
if (field.notnull or field.unique) and field_type not in excluded_fields:
|
||||||
elif not field.notnull and not field.unique and requires:
|
requires.insert(0, validators.IS_NOT_EMPTY())
|
||||||
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
|
elif not field.notnull and not field.unique and requires:
|
||||||
|
null = null='' if field.type in ('string', 'text', 'password') else None
|
||||||
|
requires[0] = validators.IS_EMPTY_OR(requires[0], null=null)
|
||||||
return requires
|
return requires
|
||||||
|
|
||||||
from gluon.serializers import custom_json, xml
|
|
||||||
from gluon.utils import web2py_uuid
|
|
||||||
from gluon import sqlhtml
|
|
||||||
|
|
||||||
|
|
||||||
DAL.serializers = {'json': custom_json, 'xml': xml}
|
DAL.serializers = {'json': custom_json, 'xml': xml}
|
||||||
DAL.validators_method = _default_validators
|
DAL.validators_method = _default_validators
|
||||||
DAL.uuid = lambda x: web2py_uuid()
|
DAL.uuid = lambda x: web2py_uuid()
|
||||||
@@ -96,8 +122,7 @@ DAL.representers = {
|
|||||||
DAL.Field = Field
|
DAL.Field = Field
|
||||||
DAL.Table = Table
|
DAL.Table = Table
|
||||||
|
|
||||||
#: add web2py contrib drivers to pyDAL
|
# add web2py contrib drivers to pyDAL
|
||||||
from pydal.drivers import DRIVERS
|
|
||||||
if not DRIVERS.get('pymysql'):
|
if not DRIVERS.get('pymysql'):
|
||||||
try:
|
try:
|
||||||
from .contrib import pymysql
|
from .contrib import pymysql
|
||||||
|
|||||||
+12
-13
@@ -15,13 +15,13 @@ import codecs
|
|||||||
# None represents a potentially variable byte. "##" in the XML spec...
|
# None represents a potentially variable byte. "##" in the XML spec...
|
||||||
autodetect_dict = { # bytepattern : ("name",
|
autodetect_dict = { # bytepattern : ("name",
|
||||||
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
|
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
|
||||||
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
|
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
|
||||||
(0xFE, 0xFF, None, None): ("utf_16_be"),
|
(0xFE, 0xFF, None, None): ("utf_16_be"),
|
||||||
(0xFF, 0xFE, None, None): ("utf_16_le"),
|
(0xFF, 0xFE, None, None): ("utf_16_le"),
|
||||||
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
|
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
|
||||||
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
|
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
|
||||||
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
|
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
|
||||||
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
|
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -36,10 +36,10 @@ def autoDetectXMLEncoding(buffer):
|
|||||||
# buffer at once but otherwise we'd have to decode a character at
|
# buffer at once but otherwise we'd have to decode a character at
|
||||||
# a time looking for the quote character...that's a pain
|
# a time looking for the quote character...that's a pain
|
||||||
|
|
||||||
encoding = "utf_8" # according to the XML spec, this is the default
|
encoding = "utf_8"
|
||||||
# this code successively tries to refine the default
|
# according to the XML spec, this is the default this code successively tries to refine the default
|
||||||
# whenever it fails to refine, it falls back to
|
# whenever it fails to refine, it falls back to the last place encoding was set.
|
||||||
# the last place encoding was set.
|
|
||||||
if len(buffer) >= 4:
|
if len(buffer) >= 4:
|
||||||
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
|
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
|
||||||
enc_info = autodetect_dict.get(bytes, None)
|
enc_info = autodetect_dict.get(bytes, None)
|
||||||
@@ -51,8 +51,7 @@ def autoDetectXMLEncoding(buffer):
|
|||||||
enc_info = None
|
enc_info = None
|
||||||
|
|
||||||
if enc_info:
|
if enc_info:
|
||||||
encoding = enc_info # we've got a guess... these are
|
encoding = enc_info # we've got a guess... these are the new defaults
|
||||||
#the new defaults
|
|
||||||
|
|
||||||
# try to find a more precise encoding using xml declaration
|
# try to find a more precise encoding using xml declaration
|
||||||
secret_decoder_ring = codecs.lookup(encoding)[1]
|
secret_decoder_ring = codecs.lookup(encoding)[1]
|
||||||
|
|||||||
+2
-2
@@ -415,10 +415,10 @@ def fix_newlines(path):
|
|||||||
|\r|
|
|\r|
|
||||||
)''')
|
)''')
|
||||||
for filename in listdir(path, '.*\.(py|html)$', drop=False):
|
for filename in listdir(path, '.*\.(py|html)$', drop=False):
|
||||||
rdata = read_file(filename, 'rb')
|
rdata = read_file(filename, 'r')
|
||||||
wdata = regex.sub('\n', rdata)
|
wdata = regex.sub('\n', rdata)
|
||||||
if wdata != rdata:
|
if wdata != rdata:
|
||||||
write_file(filename, wdata, 'wb')
|
write_file(filename, wdata, 'w')
|
||||||
|
|
||||||
|
|
||||||
def copystream(
|
def copystream(
|
||||||
|
|||||||
+96
-49
@@ -13,7 +13,8 @@ Contains the classes for the global used variables:
|
|||||||
- Session
|
- Session
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, unicodeT, long
|
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \
|
||||||
|
unicodeT, long, hashlib_md5, urllib_quote
|
||||||
from gluon.storage import Storage, List
|
from gluon.storage import Storage, List
|
||||||
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
|
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
|
||||||
from gluon.contenttype import contenttype
|
from gluon.contenttype import contenttype
|
||||||
@@ -30,7 +31,7 @@ from gluon.fileutils import copystream
|
|||||||
import hashlib
|
import hashlib
|
||||||
from pydal.contrib import portalocker
|
from pydal.contrib import portalocker
|
||||||
from pickle import Pickler, MARK, DICT, EMPTY_DICT
|
from pickle import Pickler, MARK, DICT, EMPTY_DICT
|
||||||
#from types import DictionaryType
|
# from types import DictionaryType
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
@@ -48,7 +49,7 @@ PAST = 'Sat, 1-Jan-1971 00:00:00'
|
|||||||
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
|
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#FIXME PY3
|
# FIXME PY3
|
||||||
from gluon.contrib.minify import minify
|
from gluon.contrib.minify import minify
|
||||||
have_minify = True
|
have_minify = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -79,6 +80,7 @@ template_mapping = {
|
|||||||
'js:inline': js_inline
|
'js:inline': js_inline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# IMPORTANT:
|
# IMPORTANT:
|
||||||
# this is required so that pickled dict(s) and class.__dict__
|
# this is required so that pickled dict(s) and class.__dict__
|
||||||
# are sorted and web2py can detect without ambiguity when a session changes
|
# are sorted and web2py can detect without ambiguity when a session changes
|
||||||
@@ -95,6 +97,7 @@ else:
|
|||||||
SortingPickler.dispatch_table = copyreg.dispatch_table.copy()
|
SortingPickler.dispatch_table = copyreg.dispatch_table.copy()
|
||||||
SortingPickler.dispatch_table[dict] = SortingPickler.save_dict
|
SortingPickler.dispatch_table[dict] = SortingPickler.save_dict
|
||||||
|
|
||||||
|
|
||||||
def sorting_dumps(obj, protocol=None):
|
def sorting_dumps(obj, protocol=None):
|
||||||
file = StringIO()
|
file = StringIO()
|
||||||
SortingPickler(file, protocol).dump(obj)
|
SortingPickler(file, protocol).dump(obj)
|
||||||
@@ -120,7 +123,7 @@ def copystream_progress(request, chunk_size=10 ** 5):
|
|||||||
dest = tempfile.NamedTemporaryFile()
|
dest = tempfile.NamedTemporaryFile()
|
||||||
except NotImplementedError: # and GAE this
|
except NotImplementedError: # and GAE this
|
||||||
dest = tempfile.TemporaryFile()
|
dest = tempfile.TemporaryFile()
|
||||||
if not 'X-Progress-ID' in request.get_vars:
|
if 'X-Progress-ID' not in request.get_vars:
|
||||||
copystream(source, dest, size, chunk_size)
|
copystream(source, dest, size, chunk_size)
|
||||||
return dest
|
return dest
|
||||||
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
|
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
|
||||||
@@ -198,7 +201,8 @@ class Request(Storage):
|
|||||||
"""Takes the QUERY_STRING and unpacks it to get_vars
|
"""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
|
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)
|
get_vars = self._get_vars = Storage(dget)
|
||||||
for (key, value) in iteritems(get_vars):
|
for (key, value) in iteritems(get_vars):
|
||||||
if isinstance(value, list) and len(value) == 1:
|
if isinstance(value, list) and len(value) == 1:
|
||||||
@@ -216,7 +220,12 @@ class Request(Storage):
|
|||||||
|
|
||||||
if is_json:
|
if is_json:
|
||||||
try:
|
try:
|
||||||
json_vars = json_parser.load(body)
|
# In Python 3 versions prior to 3.6 load doesn't accept bytes and
|
||||||
|
# bytearray, so we read the body convert to native and use loads
|
||||||
|
# instead of load.
|
||||||
|
# This line can be simplified to json_vars = json_parser.load(body)
|
||||||
|
# if and when we drop support for python versions under 3.6
|
||||||
|
json_vars = json_parser.loads(to_native(body.read()))
|
||||||
except:
|
except:
|
||||||
# incoherent request bodies can still be parsed "ad-hoc"
|
# incoherent request bodies can still be parsed "ad-hoc"
|
||||||
json_vars = {}
|
json_vars = {}
|
||||||
@@ -228,8 +237,7 @@ class Request(Storage):
|
|||||||
body.seek(0)
|
body.seek(0)
|
||||||
|
|
||||||
# parse POST variables on POST, PUT, BOTH only in post_vars
|
# parse POST variables on POST, PUT, BOTH only in post_vars
|
||||||
if (body and not is_json
|
if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'):
|
||||||
and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
|
|
||||||
query_string = env.pop('QUERY_STRING', None)
|
query_string = env.pop('QUERY_STRING', None)
|
||||||
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
|
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
|
||||||
try:
|
try:
|
||||||
@@ -328,11 +336,16 @@ class Request(Storage):
|
|||||||
user_agent = session._user_agent
|
user_agent = session._user_agent
|
||||||
if user_agent:
|
if user_agent:
|
||||||
return user_agent
|
return user_agent
|
||||||
user_agent = user_agent_parser.detect(self.env.http_user_agent)
|
http_user_agent = self.env.http_user_agent or ''
|
||||||
|
user_agent = user_agent_parser.detect(http_user_agent)
|
||||||
for key, value in user_agent.items():
|
for key, value in user_agent.items():
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
user_agent[key] = Storage(value)
|
user_agent[key] = Storage(value)
|
||||||
user_agent = session._user_agent = Storage(user_agent)
|
user_agent = Storage(user_agent)
|
||||||
|
user_agent.is_mobile = 'Mobile' in http_user_agent
|
||||||
|
user_agent.is_tablet = 'Tablet' in http_user_agent
|
||||||
|
session._user_agent = user_agent
|
||||||
|
|
||||||
return user_agent
|
return user_agent
|
||||||
|
|
||||||
def requires_https(self):
|
def requires_https(self):
|
||||||
@@ -355,7 +368,7 @@ class Request(Storage):
|
|||||||
def f(_action=action, *a, **b):
|
def f(_action=action, *a, **b):
|
||||||
request.is_restful = True
|
request.is_restful = True
|
||||||
env = request.env
|
env = request.env
|
||||||
is_json = env.content_type=='application/json'
|
is_json = env.content_type == 'application/json'
|
||||||
method = env.request_method
|
method = env.request_method
|
||||||
if not ignore_extension and len(request.args) and '.' in request.args[-1]:
|
if not ignore_extension and len(request.args) and '.' in request.args[-1]:
|
||||||
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
|
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
|
||||||
@@ -451,13 +464,13 @@ class Response(Storage):
|
|||||||
for meta in iteritems((self.meta or {})):
|
for meta in iteritems((self.meta or {})):
|
||||||
k, v = meta
|
k, v = meta
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), to_native(xmlescape(v[key]))) for key in v) +' />\n'
|
s += '<meta' + ''.join(' %s="%s"' % (to_native(xmlescape(key)),
|
||||||
|
to_native(xmlescape(v[key]))) for key in v) + ' />\n'
|
||||||
else:
|
else:
|
||||||
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
|
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
|
||||||
self.write(s, escape=False)
|
self.write(s, escape=False)
|
||||||
|
|
||||||
def include_files(self, extensions=None):
|
def include_files(self, extensions=None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Includes files (usually in the head).
|
Includes files (usually in the head).
|
||||||
Can minify and cache local files
|
Can minify and cache local files
|
||||||
@@ -465,46 +478,67 @@ class Response(Storage):
|
|||||||
response.cache_includes = (cache_method, time_expire).
|
response.cache_includes = (cache_method, time_expire).
|
||||||
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
||||||
"""
|
"""
|
||||||
|
app = current.request.application
|
||||||
|
|
||||||
|
# We start by building a files list in which adjacent files internal to
|
||||||
|
# the application are placed in a list inside the files list.
|
||||||
|
#
|
||||||
|
# We will only minify and concat adjacent internal files as there's
|
||||||
|
# no way to know if changing the order with which the files are apppended
|
||||||
|
# will break things since the order matters in both CSS and JS and
|
||||||
|
# internal files may be interleaved with external ones.
|
||||||
files = []
|
files = []
|
||||||
ext_files = []
|
# For the adjacent list we're going to use storage List to both distinguish
|
||||||
has_js = has_css = False
|
# from the regular list and so we can add attributes
|
||||||
|
internal = List()
|
||||||
|
internal.has_js = False
|
||||||
|
internal.has_css = False
|
||||||
|
done = set() # to remove duplicates
|
||||||
for item in self.files:
|
for item in self.files:
|
||||||
if isinstance(item, (list, tuple)):
|
if not isinstance(item, list):
|
||||||
ext_files.append(item)
|
if item in done:
|
||||||
|
continue
|
||||||
|
done.add(item)
|
||||||
|
if isinstance(item, (list, tuple)) or not item.startswith('/' + app): # also consider items in other web2py applications to be external
|
||||||
|
if internal:
|
||||||
|
files.append(internal)
|
||||||
|
internal = List()
|
||||||
|
internal.has_js = False
|
||||||
|
internal.has_css = False
|
||||||
|
files.append(item)
|
||||||
continue
|
continue
|
||||||
if extensions and not item.rpartition('.')[2] in extensions:
|
if extensions and not item.rpartition('.')[2] in extensions:
|
||||||
continue
|
continue
|
||||||
if item in files:
|
internal.append(item)
|
||||||
continue
|
|
||||||
if item.endswith('.js'):
|
if item.endswith('.js'):
|
||||||
has_js = True
|
internal.has_js = True
|
||||||
if item.endswith('.css'):
|
if item.endswith('.css'):
|
||||||
has_css = True
|
internal.has_css = True
|
||||||
files.append(item)
|
if internal:
|
||||||
|
files.append(internal)
|
||||||
|
|
||||||
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
|
# We're done we can now minify
|
||||||
# cache for 5 minutes by default
|
if have_minify:
|
||||||
key = hashlib.md5(repr(files)).hexdigest()
|
for i, f in enumerate(files):
|
||||||
|
if isinstance(f, List) and ((self.optimize_css and f.has_css) or (self.optimize_js and f.has_js)):
|
||||||
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
# cache for 5 minutes by default
|
||||||
|
key = hashlib_md5(repr(f)).hexdigest()
|
||||||
def call_minify(files=files):
|
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
||||||
return minify.minify(files,
|
def call_minify(files=f):
|
||||||
URL('static', 'temp'),
|
return List(minify.minify(files,
|
||||||
current.request.folder,
|
URL('static', 'temp'),
|
||||||
self.optimize_css,
|
current.request.folder,
|
||||||
self.optimize_js)
|
self.optimize_css,
|
||||||
if cache:
|
self.optimize_js))
|
||||||
cache_model, time_expire = cache
|
if cache:
|
||||||
files = cache_model('response.files.minified/' + key,
|
cache_model, time_expire = cache
|
||||||
call_minify,
|
files[i] = cache_model('response.files.minified/' + key,
|
||||||
time_expire)
|
call_minify,
|
||||||
else:
|
time_expire)
|
||||||
files = call_minify()
|
else:
|
||||||
|
files[i] = call_minify()
|
||||||
files.extend(ext_files)
|
|
||||||
s = []
|
def static_map(s, item):
|
||||||
for item in files:
|
|
||||||
if isinstance(item, str):
|
if isinstance(item, str):
|
||||||
f = item.lower().split('?')[0]
|
f = item.lower().split('?')[0]
|
||||||
ext = f.rpartition('.')[2]
|
ext = f.rpartition('.')[2]
|
||||||
@@ -523,6 +557,14 @@ class Response(Storage):
|
|||||||
tmpl = template_mapping.get(f)
|
tmpl = template_mapping.get(f)
|
||||||
if tmpl:
|
if tmpl:
|
||||||
s.append(tmpl % item[1])
|
s.append(tmpl % item[1])
|
||||||
|
|
||||||
|
s = []
|
||||||
|
for item in files:
|
||||||
|
if isinstance(item, List):
|
||||||
|
for f in item:
|
||||||
|
static_map(s, f)
|
||||||
|
else:
|
||||||
|
static_map(s, item)
|
||||||
self.write(''.join(s), escape=False)
|
self.write(''.join(s), escape=False)
|
||||||
|
|
||||||
def stream(self,
|
def stream(self,
|
||||||
@@ -578,9 +620,9 @@ class Response(Storage):
|
|||||||
if hasattr(stream, 'name'):
|
if hasattr(stream, 'name'):
|
||||||
filename = stream.name
|
filename = stream.name
|
||||||
|
|
||||||
if filename and not 'content-type' in keys:
|
if filename and 'content-type' not in keys:
|
||||||
headers['Content-Type'] = contenttype(filename)
|
headers['Content-Type'] = contenttype(filename)
|
||||||
if filename and not 'content-length' in keys:
|
if filename and 'content-length' not in keys:
|
||||||
try:
|
try:
|
||||||
headers['Content-Length'] = \
|
headers['Content-Length'] = \
|
||||||
os.path.getsize(filename)
|
os.path.getsize(filename)
|
||||||
@@ -638,6 +680,11 @@ class Response(Storage):
|
|||||||
if download_filename is None:
|
if download_filename is None:
|
||||||
download_filename = filename
|
download_filename = filename
|
||||||
if attachment:
|
if attachment:
|
||||||
|
# Browsers still don't have a simple uniform way to have non ascii
|
||||||
|
# characters in the filename so for now we are percent encoding it
|
||||||
|
if isinstance(download_filename, unicodeT):
|
||||||
|
download_filename = download_filename.encode('utf-8')
|
||||||
|
download_filename = urllib_quote(download_filename)
|
||||||
headers['Content-Disposition'] = \
|
headers['Content-Disposition'] = \
|
||||||
'attachment; filename="%s"' % download_filename.replace('"', '\"')
|
'attachment; filename="%s"' % download_filename.replace('"', '\"')
|
||||||
return self.stream(stream, chunk_size=chunk_size, request=request)
|
return self.stream(stream, chunk_size=chunk_size, request=request)
|
||||||
@@ -1022,7 +1069,7 @@ class Session(Storage):
|
|||||||
if self._forget:
|
if self._forget:
|
||||||
del rcookies[response.session_id_name]
|
del rcookies[response.session_id_name]
|
||||||
return
|
return
|
||||||
if self.get('httponly_cookies',True):
|
if self.get('httponly_cookies', True):
|
||||||
scookies['HttpOnly'] = True
|
scookies['HttpOnly'] = True
|
||||||
if self._secure:
|
if self._secure:
|
||||||
scookies['secure'] = True
|
scookies['secure'] = True
|
||||||
@@ -1193,7 +1240,7 @@ class Session(Storage):
|
|||||||
if (not response.session_id or
|
if (not response.session_id or
|
||||||
not response.session_filename or
|
not response.session_filename or
|
||||||
self._forget
|
self._forget
|
||||||
or self._unchanged(response)):
|
or self._unchanged(response)):
|
||||||
# self.clear_session_cookies()
|
# self.clear_session_cookies()
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
+13
-13
@@ -182,8 +182,8 @@ class Highlighter(object):
|
|||||||
)),
|
)),
|
||||||
'PYTHONMultilineString': (python_tokenizer,
|
'PYTHONMultilineString': (python_tokenizer,
|
||||||
(('ENDMULTILINESTRING',
|
(('ENDMULTILINESTRING',
|
||||||
re.compile(r'.*?("""|\'\'\')',
|
re.compile(r'.*?("""|\'\'\')',
|
||||||
re.DOTALL), 'color: darkred'), )),
|
re.DOTALL), 'color: darkred'), )),
|
||||||
'HTML': (html_tokenizer, (
|
'HTML': (html_tokenizer, (
|
||||||
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
|
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
|
||||||
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
|
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
|
||||||
@@ -209,7 +209,7 @@ class Highlighter(object):
|
|||||||
mode = self.mode
|
mode = self.mode
|
||||||
while i < len(data):
|
while i < len(data):
|
||||||
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
|
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
|
||||||
if not token in self.suppress_tokens:
|
if token not in self.suppress_tokens:
|
||||||
match = o_re.match(data, i)
|
match = o_re.match(data, i)
|
||||||
if match:
|
if match:
|
||||||
if style:
|
if style:
|
||||||
@@ -221,7 +221,7 @@ class Highlighter(object):
|
|||||||
new_mode = \
|
new_mode = \
|
||||||
Highlighter.all_styles[mode][0](self,
|
Highlighter.all_styles[mode][0](self,
|
||||||
token, match, style)
|
token, match, style)
|
||||||
if not new_mode is None:
|
if new_mode is not None:
|
||||||
mode = new_mode
|
mode = new_mode
|
||||||
i += max(1, len(match.group()))
|
i += max(1, len(match.group()))
|
||||||
break
|
break
|
||||||
@@ -241,9 +241,9 @@ class Highlighter(object):
|
|||||||
style = self.styles[token]
|
style = self.styles[token]
|
||||||
if self.span_style != style:
|
if self.span_style != style:
|
||||||
if style != 'Keep':
|
if style != 'Keep':
|
||||||
if not self.span_style is None:
|
if self.span_style is not None:
|
||||||
self.output.append('</span>')
|
self.output.append('</span>')
|
||||||
if not style is None:
|
if style is not None:
|
||||||
self.output.append('<span style="%s">' % style)
|
self.output.append('<span style="%s">' % style)
|
||||||
self.span_style = style
|
self.span_style = style
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ def highlight(
|
|||||||
):
|
):
|
||||||
styles = styles or {}
|
styles = styles or {}
|
||||||
attributes = attributes or {}
|
attributes = attributes or {}
|
||||||
if not 'CODE' in styles:
|
if 'CODE' not in styles:
|
||||||
code_style = """
|
code_style = """
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-family: Bitstream Vera Sans Mono,monospace;
|
font-family: Bitstream Vera Sans Mono,monospace;
|
||||||
@@ -272,7 +272,7 @@ def highlight(
|
|||||||
white-space: pre !important;\n"""
|
white-space: pre !important;\n"""
|
||||||
else:
|
else:
|
||||||
code_style = styles['CODE']
|
code_style = styles['CODE']
|
||||||
if not 'LINENUMBERS' in styles:
|
if 'LINENUMBERS' not in styles:
|
||||||
linenumbers_style = """
|
linenumbers_style = """
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-family: Bitstream Vera Sans Mono,monospace;
|
font-family: Bitstream Vera Sans Mono,monospace;
|
||||||
@@ -283,7 +283,7 @@ def highlight(
|
|||||||
color: #A0A0A0;\n"""
|
color: #A0A0A0;\n"""
|
||||||
else:
|
else:
|
||||||
linenumbers_style = styles['LINENUMBERS']
|
linenumbers_style = styles['LINENUMBERS']
|
||||||
if not 'LINEHIGHLIGHT' in styles:
|
if 'LINEHIGHLIGHT' not in styles:
|
||||||
linehighlight_style = "background-color: #EBDDE2;"
|
linehighlight_style = "background-color: #EBDDE2;"
|
||||||
else:
|
else:
|
||||||
linehighlight_style = styles['LINEHIGHLIGHT']
|
linehighlight_style = styles['LINEHIGHLIGHT']
|
||||||
@@ -333,8 +333,9 @@ def highlight(
|
|||||||
== '_' and value])
|
== '_' and value])
|
||||||
if fa:
|
if fa:
|
||||||
fa = ' ' + fa
|
fa = ' ' + fa
|
||||||
return '<table%s><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
|
return '<table%s><tr style="vertical-align:top;">' \
|
||||||
% (fa, linenumbers_style, numbers, code_style, code)
|
'<td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td>' \
|
||||||
|
'<td><pre style="%s">%s</pre></td></tr></table>' % (fa, linenumbers_style, numbers, code_style, code)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -342,5 +343,4 @@ if __name__ == '__main__':
|
|||||||
argfp = open(sys.argv[1])
|
argfp = open(sys.argv[1])
|
||||||
data = argfp.read()
|
data = argfp.read()
|
||||||
argfp.close()
|
argfp.close()
|
||||||
print('<html><body>' + highlight(data, sys.argv[2])\
|
print('<html><body>' + highlight(data, sys.argv[2]) + '</body></html>')
|
||||||
+ '</body></html>')
|
|
||||||
|
|||||||
+27
-22
@@ -20,7 +20,8 @@ import urllib
|
|||||||
import base64
|
import base64
|
||||||
from gluon import sanitizer, decoder
|
from gluon import sanitizer, decoder
|
||||||
import itertools
|
import itertools
|
||||||
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
|
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
|
||||||
|
urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
|
||||||
from gluon.utils import local_html_escape
|
from gluon.utils import local_html_escape
|
||||||
import marshal
|
import marshal
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ __all__ = [
|
|||||||
|
|
||||||
DEFAULT_PASSWORD_DISPLAY = '*' * 8
|
DEFAULT_PASSWORD_DISPLAY = '*' * 8
|
||||||
|
|
||||||
|
|
||||||
def xmlescape(data, quote=True):
|
def xmlescape(data, quote=True):
|
||||||
"""
|
"""
|
||||||
Returns an escaped string of the provided data
|
Returns an escaped string of the provided data
|
||||||
@@ -124,10 +126,9 @@ def xmlescape(data, quote=True):
|
|||||||
|
|
||||||
if not(isinstance(data, (text_type, bytes))):
|
if not(isinstance(data, (text_type, bytes))):
|
||||||
# i.e., integers
|
# i.e., integers
|
||||||
data=str(data)
|
data = str(data)
|
||||||
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
|
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
# ... and do the escaping
|
# ... and do the escaping
|
||||||
data = local_html_escape(data, quote)
|
data = local_html_escape(data, quote)
|
||||||
return data
|
return data
|
||||||
@@ -671,6 +672,7 @@ def XML_pickle(data):
|
|||||||
return XML_unpickle, (marshal.dumps(str(data)),)
|
return XML_unpickle, (marshal.dumps(str(data)),)
|
||||||
copyreg.pickle(XML, XML_pickle, XML_unpickle)
|
copyreg.pickle(XML, XML_pickle, XML_unpickle)
|
||||||
|
|
||||||
|
|
||||||
@implements_bool
|
@implements_bool
|
||||||
class DIV(XmlComponent):
|
class DIV(XmlComponent):
|
||||||
"""
|
"""
|
||||||
@@ -1309,8 +1311,10 @@ class HTML(DIV):
|
|||||||
tag = b'html'
|
tag = b'html'
|
||||||
|
|
||||||
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
|
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
|
||||||
transitional = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
|
transitional = \
|
||||||
frameset = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
|
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
|
||||||
|
frameset = \
|
||||||
|
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
|
||||||
html5 = b'<!DOCTYPE HTML>\n'
|
html5 = b'<!DOCTYPE HTML>\n'
|
||||||
|
|
||||||
def xml(self):
|
def xml(self):
|
||||||
@@ -1415,20 +1419,21 @@ class LINK(DIV):
|
|||||||
|
|
||||||
class SCRIPT(DIV):
|
class SCRIPT(DIV):
|
||||||
|
|
||||||
tag = 'script'
|
tag = b'script'
|
||||||
|
tagname = to_bytes(tag)
|
||||||
|
|
||||||
def xml(self):
|
def xml(self):
|
||||||
(fa, co) = self._xml()
|
(fa, co) = self._xml()
|
||||||
fa = to_native(fa)
|
fa = to_bytes(fa)
|
||||||
# no escaping of subcomponents
|
# no escaping of subcomponents
|
||||||
co = '\n'.join([str(component) for component in
|
co = b'\n'.join([to_bytes(component) for component in
|
||||||
self.components])
|
self.components])
|
||||||
if co:
|
if co:
|
||||||
# <script [attributes]><!--//--><![CDATA[//><!--
|
# <script [attributes]><!--//--><![CDATA[//><!--
|
||||||
# script body
|
# script body
|
||||||
# //--><!]]></script>
|
# //--><!]]></script>
|
||||||
# return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
|
# return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
|
||||||
return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
|
return b'<%s%s><!--\n%s\n//--></%s>' % (self.tagname, fa, co, self.tagname)
|
||||||
else:
|
else:
|
||||||
return DIV.xml(self)
|
return DIV.xml(self)
|
||||||
|
|
||||||
@@ -1436,18 +1441,19 @@ class SCRIPT(DIV):
|
|||||||
class STYLE(DIV):
|
class STYLE(DIV):
|
||||||
|
|
||||||
tag = 'style'
|
tag = 'style'
|
||||||
|
tagname = to_bytes(tag)
|
||||||
|
|
||||||
def xml(self):
|
def xml(self):
|
||||||
(fa, co) = self._xml()
|
(fa, co) = self._xml()
|
||||||
fa = to_native(fa)
|
fa = to_bytes(fa)
|
||||||
# no escaping of subcomponents
|
# no escaping of subcomponents
|
||||||
co = '\n'.join([str(component) for component in
|
co = b'\n'.join([to_bytes(component) for component in
|
||||||
self.components])
|
self.components])
|
||||||
if co:
|
if co:
|
||||||
# <style [attributes]><!--/*--><![CDATA[/*><!--*/
|
# <style [attributes]><!--/*--><![CDATA[/*><!--*/
|
||||||
# style body
|
# style body
|
||||||
# /*]]>*/--></style>
|
# /*]]>*/--></style>
|
||||||
return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
|
return b'<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tagname, fa, co, self.tagname)
|
||||||
else:
|
else:
|
||||||
return DIV.xml(self)
|
return DIV.xml(self)
|
||||||
|
|
||||||
@@ -1861,7 +1867,7 @@ class INPUT(DIV):
|
|||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
msg = "Validation error, field:%s %s" % (name,validator)
|
msg = "Validation error, field:%s %s" % (name, validator)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
if errors is not None:
|
if errors is not None:
|
||||||
self.vars[name] = value
|
self.vars[name] = value
|
||||||
@@ -1911,7 +1917,7 @@ class INPUT(DIV):
|
|||||||
name = self.attributes.get('_name', None)
|
name = self.attributes.get('_name', None)
|
||||||
if name and hasattr(self, 'errors') \
|
if name and hasattr(self, 'errors') \
|
||||||
and self.errors.get(name, None) \
|
and self.errors.get(name, None) \
|
||||||
and self['hideerror'] != True:
|
and self['hideerror'] is not True:
|
||||||
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
|
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
|
||||||
return DIV.xml(self) + DIV(
|
return DIV.xml(self) + DIV(
|
||||||
DIV(
|
DIV(
|
||||||
@@ -1979,7 +1985,6 @@ class OPTGROUP(DIV):
|
|||||||
|
|
||||||
|
|
||||||
class SELECT(INPUT):
|
class SELECT(INPUT):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@@ -2014,7 +2019,7 @@ class SELECT(INPUT):
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
if not self['_multiple']:
|
if not self['_multiple']:
|
||||||
for c in options: # my patch
|
for c in options: # my patch
|
||||||
if ((value is not None) and (str(c['_value']) == str(value))):
|
if (value is not None) and (str(c['_value']) == str(value)):
|
||||||
c['_selected'] = 'selected'
|
c['_selected'] = 'selected'
|
||||||
else:
|
else:
|
||||||
c['_selected'] = None
|
c['_selected'] = None
|
||||||
@@ -2024,7 +2029,7 @@ class SELECT(INPUT):
|
|||||||
else:
|
else:
|
||||||
values = [str(value)]
|
values = [str(value)]
|
||||||
for c in options: # my patch
|
for c in options: # my patch
|
||||||
if ((value is not None) and (str(c['_value']) in values)):
|
if (value is not None) and (str(c['_value']) in values):
|
||||||
c['_selected'] = 'selected'
|
c['_selected'] = 'selected'
|
||||||
else:
|
else:
|
||||||
c['_selected'] = None
|
c['_selected'] = None
|
||||||
@@ -2390,7 +2395,6 @@ class FORM(DIV):
|
|||||||
|
|
||||||
|
|
||||||
class BEAUTIFY(DIV):
|
class BEAUTIFY(DIV):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Turns any list, dictionary, etc into decent looking html.
|
Turns any list, dictionary, etc into decent looking html.
|
||||||
|
|
||||||
@@ -2429,7 +2433,7 @@ class BEAUTIFY(DIV):
|
|||||||
if level == 0:
|
if level == 0:
|
||||||
return
|
return
|
||||||
for c in self.components:
|
for c in self.components:
|
||||||
if hasattr(c, 'value') and not callable(c.value):
|
if hasattr(c, 'value') and not callable(c.value) and not isinstance(c, cgi.FieldStorage):
|
||||||
if c.value:
|
if c.value:
|
||||||
components.append(c.value)
|
components.append(c.value)
|
||||||
if hasattr(c, 'xml') and callable(c.xml):
|
if hasattr(c, 'xml') and callable(c.xml):
|
||||||
@@ -2547,7 +2551,7 @@ class MENU(DIV):
|
|||||||
li['_class'] = li['_class'] + ' ' + self['li_active']
|
li['_class'] = li['_class'] + ' ' + self['li_active']
|
||||||
else:
|
else:
|
||||||
li['_class'] = self['li_active']
|
li['_class'] = self['li_active']
|
||||||
if len(item) <= 4 or item[4] == True:
|
if len(item) <= 4 or item[4] is True:
|
||||||
ul.append(li)
|
ul.append(li)
|
||||||
return ul
|
return ul
|
||||||
|
|
||||||
@@ -2561,7 +2565,7 @@ class MENU(DIV):
|
|||||||
# ex: ('', False, A('title', _href=URL(...), _title="title"))
|
# ex: ('', False, A('title', _href=URL(...), _title="title"))
|
||||||
# ex: (A('title', _href=URL(...), _title="title"), False, None)
|
# ex: (A('title', _href=URL(...), _title="title"), False, None)
|
||||||
custom_items.append(item)
|
custom_items.append(item)
|
||||||
elif len(item) <= 4 or item[4] == True:
|
elif len(item) <= 4 or item[4] is True:
|
||||||
select.append(OPTION(CAT(prefix, item[0]),
|
select.append(OPTION(CAT(prefix, item[0]),
|
||||||
_value=item[2], _selected=item[1]))
|
_value=item[2], _selected=item[1]))
|
||||||
if len(item) > 3 and len(item[3]):
|
if len(item) > 3 and len(item[3]):
|
||||||
@@ -2703,7 +2707,8 @@ class web2pyHTMLParser(HTMLParser):
|
|||||||
self.parent = self.parent.parent
|
self.parent = self.parent.parent
|
||||||
except:
|
except:
|
||||||
raise RuntimeError("unable to balance tag %s" % tagname)
|
raise RuntimeError("unable to balance tag %s" % tagname)
|
||||||
if parent_tagname[:len(tagname)] == tagname: break
|
if parent_tagname[:len(tagname)] == tagname:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def markdown_serializer(text, tag=None, attr=None):
|
def markdown_serializer(text, tag=None, attr=None):
|
||||||
|
|||||||
+11
-4
@@ -11,7 +11,7 @@ HTTP statuses helpers
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from gluon._compat import iteritems
|
from gluon._compat import iteritems, unicodeT, to_bytes
|
||||||
|
|
||||||
__all__ = ['HTTP', 'redirect']
|
__all__ = ['HTTP', 'redirect']
|
||||||
|
|
||||||
@@ -111,22 +111,29 @@ class HTTP(Exception):
|
|||||||
if not body:
|
if not body:
|
||||||
body = status
|
body = status
|
||||||
if isinstance(body, (str, bytes, bytearray)):
|
if isinstance(body, (str, bytes, bytearray)):
|
||||||
|
if isinstance(body, unicodeT):
|
||||||
|
body = to_bytes(body) # This must be done before len
|
||||||
headers['Content-Length'] = len(body)
|
headers['Content-Length'] = len(body)
|
||||||
rheaders = []
|
rheaders = []
|
||||||
for k, v in iteritems(headers):
|
for k, v in iteritems(headers):
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
rheaders += [(k, str(item)) for item in v]
|
rheaders += [(k, str(item)) for item in v]
|
||||||
elif not v is None:
|
elif v is not None:
|
||||||
rheaders.append((k, str(v)))
|
rheaders.append((k, str(v)))
|
||||||
responder(status, rheaders)
|
responder(status, rheaders)
|
||||||
if env.get('request_method', '') == 'HEAD':
|
if env.get('request_method', '') == 'HEAD':
|
||||||
return ['']
|
return ['']
|
||||||
elif isinstance(body, (str, bytes, bytearray)):
|
elif isinstance(body, (str, bytes, bytearray)):
|
||||||
|
if isinstance(body, unicodeT):
|
||||||
|
body = to_bytes(body)
|
||||||
return [body]
|
return [body]
|
||||||
elif hasattr(body, '__iter__'):
|
elif hasattr(body, '__iter__'):
|
||||||
return body
|
return body
|
||||||
else:
|
else:
|
||||||
return [str(body)]
|
body = str(body)
|
||||||
|
if isinstance(body, unicodeT):
|
||||||
|
body = to_bytes(body)
|
||||||
|
return [body]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self):
|
def message(self):
|
||||||
@@ -148,7 +155,7 @@ class HTTP(Exception):
|
|||||||
web2py_error=self.headers.get('web2py_error'))
|
web2py_error=self.headers.get('web2py_error'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"stringify me"
|
"""stringify me"""
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -78,13 +78,9 @@ alert_dependency = ['hashlib', 'uuid']
|
|||||||
# Now we remove the blacklisted modules if we are using the stated
|
# Now we remove the blacklisted modules if we are using the stated
|
||||||
# python version.
|
# python version.
|
||||||
#
|
#
|
||||||
# List of modules deprecated in Python 2.6 or 2.7 that are in the above set
|
# List of modules deprecated in Python 2.7 that are in the above list
|
||||||
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
|
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
|
||||||
|
|
||||||
if python_version >= '2.6':
|
|
||||||
base_modules += ['json', 'multiprocessing']
|
|
||||||
base_modules = list(set(base_modules).difference(set(py26_deprecated)))
|
|
||||||
|
|
||||||
if python_version >= '2.7':
|
if python_version >= '2.7':
|
||||||
base_modules += ['argparse', 'json', 'multiprocessing']
|
base_modules += ['argparse', 'json', 'multiprocessing']
|
||||||
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
|
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
|
||||||
|
|||||||
+17
-6
@@ -18,10 +18,11 @@ import pkgutil
|
|||||||
import logging
|
import logging
|
||||||
from cgi import escape
|
from cgi import escape
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from gluon.utf8 import Utf8
|
|
||||||
from gluon.utils import local_html_escape
|
from gluon.utils import local_html_escape
|
||||||
|
|
||||||
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
|
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
|
||||||
|
|
||||||
from pydal.contrib.portalocker import read_locked, LockedFile
|
from pydal.contrib.portalocker import read_locked, LockedFile
|
||||||
|
|
||||||
from gluon.fileutils import listdir
|
from gluon.fileutils import listdir
|
||||||
@@ -49,8 +50,10 @@ DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word
|
|||||||
|
|
||||||
if PY2:
|
if PY2:
|
||||||
NUMBERS = (int, long, float)
|
NUMBERS = (int, long, float)
|
||||||
|
from gluon.utf8 import Utf8
|
||||||
else:
|
else:
|
||||||
NUMBERS = (int, float)
|
NUMBERS = (int, float)
|
||||||
|
Utf8 = str
|
||||||
|
|
||||||
# pattern to find T(blah blah blah) expressions
|
# pattern to find T(blah blah blah) expressions
|
||||||
PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
|
PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
|
||||||
@@ -107,15 +110,17 @@ def markmin(s):
|
|||||||
|
|
||||||
|
|
||||||
def upper_fun(s):
|
def upper_fun(s):
|
||||||
return unicode(s, 'utf-8').upper().encode('utf-8')
|
return to_unicode(s).upper()
|
||||||
|
|
||||||
|
|
||||||
def title_fun(s):
|
def title_fun(s):
|
||||||
return unicode(s, 'utf-8').title().encode('utf-8')
|
return to_unicode(s).title()
|
||||||
|
|
||||||
|
|
||||||
def cap_fun(s):
|
def cap_fun(s):
|
||||||
return unicode(s, 'utf-8').capitalize().encode('utf-8')
|
return to_unicode(s).capitalize()
|
||||||
|
|
||||||
|
|
||||||
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
|
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
|
||||||
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
|
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
|
||||||
|
|
||||||
@@ -426,10 +431,16 @@ class lazyT(object):
|
|||||||
return str(self) if self.M else local_html_escape(str(self), quote=False)
|
return str(self) if self.M else local_html_escape(str(self), quote=False)
|
||||||
|
|
||||||
def encode(self, *a, **b):
|
def encode(self, *a, **b):
|
||||||
return str(self).encode(*a, **b)
|
if PY2 and a[0] != 'utf8':
|
||||||
|
return to_unicode(str(self)).encode(*a, **b)
|
||||||
|
else:
|
||||||
|
return str(self)
|
||||||
|
|
||||||
def decode(self, *a, **b):
|
def decode(self, *a, **b):
|
||||||
return str(self).decode(*a, **b)
|
if PY2:
|
||||||
|
return str(self).decode(*a, **b)
|
||||||
|
else:
|
||||||
|
return str(self)
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|||||||
+29
-29
@@ -11,7 +11,8 @@ The gluon wsgi application
|
|||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
|
if False:
|
||||||
|
import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -26,7 +27,7 @@ import random
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
from gluon._compat import Cookie, urllib2
|
from gluon._compat import Cookie, urllib2
|
||||||
#from thread import allocate_lock
|
# from thread import allocate_lock
|
||||||
|
|
||||||
from gluon.fileutils import abspath, write_file
|
from gluon.fileutils import abspath, write_file
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
@@ -67,14 +68,14 @@ import gluon.messageboxhandler
|
|||||||
logging.gluon = gluon
|
logging.gluon = gluon
|
||||||
# so we must restore it! Thanks ozancag
|
# so we must restore it! Thanks ozancag
|
||||||
import locale
|
import locale
|
||||||
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
|
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
|
||||||
|
|
||||||
exists = os.path.exists
|
exists = os.path.exists
|
||||||
pjoin = os.path.join
|
pjoin = os.path.join
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.config.fileConfig(abspath("logging.conf"))
|
logging.config.fileConfig(abspath("logging.conf"))
|
||||||
except: # fails on GAE or when logfile is missing
|
except: # fails on GAE or when logfile is missing
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
@@ -182,12 +183,13 @@ def serve_controller(request, response, session):
|
|||||||
response._view_environment.update(page)
|
response._view_environment.update(page)
|
||||||
page = run_view_in(response._view_environment)
|
page = run_view_in(response._view_environment)
|
||||||
|
|
||||||
# logic to garbage collect after exec, not always, once every 100 requests
|
if not request.env.web2py_disable_garbage_collect:
|
||||||
global requests
|
# logic to garbage collect after exec, not always, once every 100 requests
|
||||||
requests = ('requests' in globals()) and (requests + 1) % 100 or 0
|
global requests
|
||||||
if not requests:
|
requests = ('requests' in globals()) and (requests + 1) % 100 or 0
|
||||||
gc.collect()
|
if not requests:
|
||||||
# end garbage collection logic
|
gc.collect()
|
||||||
|
# end garbage collection logic
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
# set default headers it not set
|
# set default headers it not set
|
||||||
@@ -230,7 +232,7 @@ class LazyWSGI(object):
|
|||||||
|
|
||||||
to call third party WSGI applications
|
to call third party WSGI applications
|
||||||
"""
|
"""
|
||||||
self.response.status = str(status).split(' ', 1)[0]
|
self.response.status = int(str(status).split(' ', 1)[0])
|
||||||
self.response.headers = dict(headers)
|
self.response.headers = dict(headers)
|
||||||
return lambda *args, **kargs: \
|
return lambda *args, **kargs: \
|
||||||
self.response.write(escape=False, *args, **kargs)
|
self.response.write(escape=False, *args, **kargs)
|
||||||
@@ -254,6 +256,7 @@ class LazyWSGI(object):
|
|||||||
return [data]
|
return [data]
|
||||||
for item in middleware_apps:
|
for item in middleware_apps:
|
||||||
app = item(app)
|
app = item(app)
|
||||||
|
|
||||||
def caller(app):
|
def caller(app):
|
||||||
return app(self.environ, self.start_response)
|
return app(self.environ, self.start_response)
|
||||||
return lambda caller=caller, app=app: caller(app)
|
return lambda caller=caller, app=app: caller(app)
|
||||||
@@ -294,9 +297,9 @@ def wsgibase(environ, responder):
|
|||||||
response = Response()
|
response = Response()
|
||||||
session = Session()
|
session = Session()
|
||||||
env = request.env
|
env = request.env
|
||||||
#env.web2py_path = global_settings.applications_parent
|
# env.web2py_path = global_settings.applications_parent
|
||||||
env.web2py_version = web2py_version
|
env.web2py_version = web2py_version
|
||||||
#env.update(global_settings)
|
# env.update(global_settings)
|
||||||
static_file = False
|
static_file = False
|
||||||
http_response = None
|
http_response = None
|
||||||
try:
|
try:
|
||||||
@@ -325,7 +328,6 @@ def wsgibase(environ, responder):
|
|||||||
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
|
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
|
||||||
response.stream(static_file, request=request)
|
response.stream(static_file, request=request)
|
||||||
|
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
# fill in request items
|
# fill in request items
|
||||||
# ##################################################
|
# ##################################################
|
||||||
@@ -356,17 +358,15 @@ def wsgibase(environ, responder):
|
|||||||
cmd_opts = global_settings.cmd_options
|
cmd_opts = global_settings.cmd_options
|
||||||
|
|
||||||
request.update(
|
request.update(
|
||||||
client = client,
|
client=client,
|
||||||
folder = abspath('applications', app) + os.sep,
|
folder=abspath('applications', app) + os.sep,
|
||||||
ajax = x_req_with == 'xmlhttprequest',
|
ajax=x_req_with == 'xmlhttprequest',
|
||||||
cid = env.http_web2py_component_element,
|
cid=env.http_web2py_component_element,
|
||||||
is_local = (env.remote_addr in local_hosts and
|
is_local=(env.remote_addr in local_hosts and client == env.remote_addr),
|
||||||
client == env.remote_addr),
|
is_shell=False,
|
||||||
is_shell = False,
|
is_scheduler=False,
|
||||||
is_scheduler = False,
|
is_https=env.wsgi_url_scheme in HTTPS_SCHEMES or
|
||||||
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
|
request.env.http_x_forwarded_proto in HTTPS_SCHEMES or env.https == 'on'
|
||||||
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
|
||||||
or env.https == 'on'
|
|
||||||
)
|
)
|
||||||
request.url = environ['PATH_INFO']
|
request.url = environ['PATH_INFO']
|
||||||
|
|
||||||
@@ -390,7 +390,7 @@ def wsgibase(environ, responder):
|
|||||||
% 'invalid request',
|
% 'invalid request',
|
||||||
web2py_error='invalid application')
|
web2py_error='invalid application')
|
||||||
elif not request.is_local and exists(disabled):
|
elif not request.is_local and exists(disabled):
|
||||||
five0three = os.path.join(request.folder,'static','503.html')
|
five0three = os.path.join(request.folder, 'static', '503.html')
|
||||||
if os.path.exists(five0three):
|
if os.path.exists(five0three):
|
||||||
raise HTTP(503, file(five0three, 'r').read())
|
raise HTTP(503, file(five0three, 'r').read())
|
||||||
else:
|
else:
|
||||||
@@ -406,7 +406,7 @@ def wsgibase(environ, responder):
|
|||||||
# get the GET and POST data
|
# get the GET and POST data
|
||||||
# ##################################################
|
# ##################################################
|
||||||
|
|
||||||
#parse_get_post_vars(request, environ)
|
# parse_get_post_vars(request, environ)
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
# expose wsgi hooks for convenience
|
# expose wsgi hooks for convenience
|
||||||
@@ -625,7 +625,7 @@ def appfactory(wsgiapp=wsgibase,
|
|||||||
raise BaseException("Can't create dir %s" % profiler_dir)
|
raise BaseException("Can't create dir %s" % profiler_dir)
|
||||||
filepath = pjoin(profiler_dir, 'wtest')
|
filepath = pjoin(profiler_dir, 'wtest')
|
||||||
try:
|
try:
|
||||||
filehandle = open( filepath, 'w' )
|
filehandle = open(filepath, 'w')
|
||||||
filehandle.close()
|
filehandle.close()
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
except IOError:
|
except IOError:
|
||||||
@@ -746,7 +746,7 @@ class HttpServer(object):
|
|||||||
sock_list = [ip, port]
|
sock_list = [ip, port]
|
||||||
if not ssl_certificate or not ssl_private_key:
|
if not ssl_certificate or not ssl_private_key:
|
||||||
logger.info('SSL is off')
|
logger.info('SSL is off')
|
||||||
elif not rocket.ssl:
|
elif not rocket.has_ssl:
|
||||||
logger.warning('Python "ssl" module unavailable. SSL is OFF')
|
logger.warning('Python "ssl" module unavailable. SSL is OFF')
|
||||||
elif not exists(ssl_certificate):
|
elif not exists(ssl_certificate):
|
||||||
logger.warning('unable to open SSL certificate. SSL is OFF')
|
logger.warning('unable to open SSL certificate. SSL is OFF')
|
||||||
|
|||||||
+1
-1
@@ -225,7 +225,7 @@ def parsecronline(line):
|
|||||||
params = line.strip().split(None, 6)
|
params = line.strip().split(None, 6)
|
||||||
if len(params) < 7:
|
if len(params) < 7:
|
||||||
return None
|
return None
|
||||||
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3,
|
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3,
|
||||||
'thu': 4, 'fri': 5, 'sat': 6}
|
'thu': 4, 'fri': 5, 'sat': 6}
|
||||||
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
|
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
|
||||||
if not s in [None, '*']:
|
if not s in [None, '*']:
|
||||||
|
|||||||
+1
-1
Submodule gluon/packages/dal updated: d75d5cf5f3...f0b2df050a
+8
-6
@@ -10,7 +10,7 @@ Restricted environment to execute application's code
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from gluon._compat import pickle, ClassType
|
from gluon._compat import pickle, ClassType, unicodeT, to_bytes
|
||||||
import traceback
|
import traceback
|
||||||
import types
|
import types
|
||||||
import os
|
import os
|
||||||
@@ -192,10 +192,10 @@ class RestrictedError(Exception):
|
|||||||
# safely show an useful message to the user
|
# safely show an useful message to the user
|
||||||
try:
|
try:
|
||||||
output = self.output
|
output = self.output
|
||||||
if isinstance(output, unicode):
|
if not isinstance(output, str, bytes, bytearray):
|
||||||
output = output.encode("utf8")
|
|
||||||
elif not isinstance(output, str):
|
|
||||||
output = str(output)
|
output = str(output)
|
||||||
|
if isinstance(output, unicodeT):
|
||||||
|
output = to_bytes(output)
|
||||||
except:
|
except:
|
||||||
output = ""
|
output = ""
|
||||||
return output
|
return output
|
||||||
@@ -205,7 +205,7 @@ def compile2(code, layer):
|
|||||||
return compile(code, layer, 'exec')
|
return compile(code, layer, 'exec')
|
||||||
|
|
||||||
|
|
||||||
def restricted(ccode, environment=None, layer='Unknown'):
|
def restricted(ccode, environment=None, layer='Unknown', scode=None):
|
||||||
"""
|
"""
|
||||||
Runs code in environment and returns the output. If an exception occurs
|
Runs code in environment and returns the output. If an exception occurs
|
||||||
in code it raises a RestrictedError containing the traceback. Layer is
|
in code it raises a RestrictedError containing the traceback. Layer is
|
||||||
@@ -230,7 +230,9 @@ def restricted(ccode, environment=None, layer='Unknown'):
|
|||||||
sys.excepthook(etype, evalue, tb)
|
sys.excepthook(etype, evalue, tb)
|
||||||
del tb
|
del tb
|
||||||
output = "%s %s" % (etype, evalue)
|
output = "%s %s" % (etype, evalue)
|
||||||
raise RestrictedError(layer, ccode, output, environment)
|
# Save source code in ticket when available
|
||||||
|
scode = scode if scode else ccode
|
||||||
|
raise RestrictedError(layer, scode, output, environment)
|
||||||
|
|
||||||
|
|
||||||
def snapshot(info=None, context=5, code=None, environment=None):
|
def snapshot(info=None, context=5, code=None, environment=None):
|
||||||
|
|||||||
+5
-7
@@ -22,12 +22,11 @@ import re
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import threading
|
import threading
|
||||||
import urllib
|
|
||||||
from gluon.storage import Storage, List
|
from gluon.storage import Storage, List
|
||||||
from gluon.http import HTTP
|
from gluon.http import HTTP
|
||||||
from gluon.fileutils import abspath, read_file
|
from gluon.fileutils import abspath, read_file
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
from gluon._compat import urllib_unquote, urllib_quote, iteritems, xrange
|
from gluon._compat import urllib_unquote, urllib_quote, iteritems, xrange, urllib_quote_plus
|
||||||
|
|
||||||
isdir = os.path.isdir
|
isdir = os.path.isdir
|
||||||
isfile = os.path.isfile
|
isfile = os.path.isfile
|
||||||
@@ -206,8 +205,7 @@ def url_out(request, environ, application, controller, function,
|
|||||||
if host is True or (host is None and (scheme or port is not None)):
|
if host is True or (host is None and (scheme or port is not None)):
|
||||||
host = request.env.http_host
|
host = request.env.http_host
|
||||||
if not scheme or scheme is True:
|
if not scheme or scheme is True:
|
||||||
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
|
scheme = request.env.get('wsgi_url_scheme', 'http').lower() if request else 'http'
|
||||||
if request else 'http'
|
|
||||||
if host:
|
if host:
|
||||||
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
|
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
|
||||||
url = '%s://%s%s' % (scheme, host_port, url)
|
url = '%s://%s%s' % (scheme, host_port, url)
|
||||||
@@ -236,7 +234,7 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
|
|||||||
path_info, query_string = uri, ''
|
path_info, query_string = uri, ''
|
||||||
query_string += \
|
query_string += \
|
||||||
'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
||||||
(status, ticket, urllib.quote_plus(
|
(status, ticket, urllib_quote_plus(
|
||||||
request.env.request_uri), request.url)
|
request.env.request_uri), request.url)
|
||||||
if uri.startswith('http://') or uri.startswith('https://'):
|
if uri.startswith('http://') or uri.startswith('https://'):
|
||||||
# make up a response
|
# make up a response
|
||||||
@@ -271,12 +269,12 @@ def try_redirect_on_error(http_object, request, ticket=None):
|
|||||||
elif '?' in redir:
|
elif '?' in redir:
|
||||||
url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
||||||
(redir, status, ticket,
|
(redir, status, ticket,
|
||||||
urllib.quote_plus(request.env.request_uri),
|
urllib_quote_plus(request.env.request_uri),
|
||||||
request.url)
|
request.url)
|
||||||
else:
|
else:
|
||||||
url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
||||||
(redir, status, ticket,
|
(redir, status, ticket,
|
||||||
urllib.quote_plus(request.env.request_uri),
|
urllib_quote_plus(request.env.request_uri),
|
||||||
request.url)
|
request.url)
|
||||||
return HTTP(303, 'You are being redirected <a href="%s">here</a>' % url, Location=url)
|
return HTTP(303, 'You are being redirected <a href="%s">here</a>' % url, Location=url)
|
||||||
return http_object
|
return http_object
|
||||||
|
|||||||
+1
-1
@@ -1750,7 +1750,7 @@ class WSGIWorker(Worker):
|
|||||||
if self.request_method != 'HEAD':
|
if self.request_method != 'HEAD':
|
||||||
try:
|
try:
|
||||||
if self.chunked:
|
if self.chunked:
|
||||||
self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data)))
|
self.conn.sendall(b'%x\r\n%s\r\n' % (len(data), to_bytes(data, 'ISO-8859-1')))
|
||||||
else:
|
else:
|
||||||
self.conn.sendall(to_bytes(data))
|
self.conn.sendall(to_bytes(data))
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
|
|||||||
+1
-1
@@ -145,7 +145,7 @@ class XssCleaner(HTMLParser):
|
|||||||
if url.startswith('#'):
|
if url.startswith('#'):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
parsed = urlparse(url)
|
parsed = urlparse.urlparse(url)
|
||||||
return ((parsed[0] in self.allowed_schemes and '.' in parsed[1]) or
|
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] in self.allowed_schemes and '@' in parsed[2]) or
|
||||||
(parsed[0] == '' and parsed[2].startswith('/')))
|
(parsed[0] == '' and parsed[2].startswith('/')))
|
||||||
|
|||||||
+2
-2
@@ -525,7 +525,7 @@ class MetaScheduler(threading.Thread):
|
|||||||
self.have_heartbeat = True # set to False to kill
|
self.have_heartbeat = True # set to False to kill
|
||||||
self.empty_runs = 0
|
self.empty_runs = 0
|
||||||
|
|
||||||
def async(self, task):
|
def local_async(self, task):
|
||||||
"""Start the background process.
|
"""Start the background process.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -913,7 +913,7 @@ class Scheduler(MetaScheduler):
|
|||||||
self.w_stats.empty_runs = 0
|
self.w_stats.empty_runs = 0
|
||||||
self.w_stats.status = RUNNING
|
self.w_stats.status = RUNNING
|
||||||
self.w_stats.total += 1
|
self.w_stats.total += 1
|
||||||
self.wrapped_report_task(task, self.async(task))
|
self.wrapped_report_task(task, self.local_async(task))
|
||||||
if not self.w_stats.status == DISABLED:
|
if not self.w_stats.status == DISABLED:
|
||||||
self.w_stats.status = ACTIVE
|
self.w_stats.status = ACTIVE
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ def xml(value, encoding='UTF-8', key='document', quote=True):
|
|||||||
return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value, key, quote))
|
return ('<?xml version="1.0" encoding="%s"?>' % encoding) + str(xml_rec(value, key, quote))
|
||||||
|
|
||||||
|
|
||||||
def json(value, default=custom_json, indent=None):
|
def json(value, default=custom_json, indent=None, sort_keys=False):
|
||||||
value = json_parser.dumps(value, default=default, sort_keys=True, indent=indent)
|
value = json_parser.dumps(value, default=default, sort_keys=sort_keys, indent=indent)
|
||||||
# replace JavaScript incompatible spacing
|
# replace JavaScript incompatible spacing
|
||||||
# http://timelessrepo.com/json-isnt-a-javascript-subset
|
# http://timelessrepo.com/json-isnt-a-javascript-subset
|
||||||
# PY3 FIXME
|
# PY3 FIXME
|
||||||
|
|||||||
+7
-1
@@ -31,10 +31,16 @@ from gluon.globals import Request, Response, Session
|
|||||||
from gluon.storage import Storage, List
|
from gluon.storage import Storage, List
|
||||||
from gluon.admin import w2p_unpack
|
from gluon.admin import w2p_unpack
|
||||||
from pydal.base import BaseAdapter
|
from pydal.base import BaseAdapter
|
||||||
from gluon._compat import iteritems, ClassType
|
from gluon._compat import iteritems, ClassType, PY2
|
||||||
|
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
|
if not PY2:
|
||||||
|
def execfile(filename, global_vars=None, local_vars=None):
|
||||||
|
with open(filename) as f:
|
||||||
|
code = compile(f.read(), filename, 'exec')
|
||||||
|
exec(code, global_vars, local_vars)
|
||||||
|
|
||||||
|
|
||||||
def enable_autocomplete_and_history(adir, env):
|
def enable_autocomplete_and_history(adir, env):
|
||||||
try:
|
try:
|
||||||
|
|||||||
+153
-30
@@ -15,11 +15,11 @@ Holds:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import urllib
|
|
||||||
import re
|
import re
|
||||||
|
import copy
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT, to_native, to_unicode
|
from gluon._compat import StringIO, unichr, urllib_quote, iteritems, basestring, long, unicodeT, to_native, to_unicode, urlencode
|
||||||
from gluon.http import HTTP, redirect
|
from gluon.http import HTTP, redirect
|
||||||
from gluon.html import XmlComponent, truncate_string
|
from gluon.html import XmlComponent, truncate_string
|
||||||
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
|
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
|
||||||
@@ -405,7 +405,7 @@ class RadioWidget(OptionsWidget):
|
|||||||
cols = attributes.get('cols', 1)
|
cols = attributes.get('cols', 1)
|
||||||
totals = len(options)
|
totals = len(options)
|
||||||
mods = totals % cols
|
mods = totals % cols
|
||||||
rows = totals / cols
|
rows = totals // cols
|
||||||
if mods:
|
if mods:
|
||||||
rows += 1
|
rows += 1
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ class CheckboxesWidget(OptionsWidget):
|
|||||||
cols = attributes.get('cols', 1)
|
cols = attributes.get('cols', 1)
|
||||||
totals = len(options)
|
totals = len(options)
|
||||||
mods = totals % cols
|
mods = totals % cols
|
||||||
rows = totals / cols
|
rows = totals // cols
|
||||||
if mods:
|
if mods:
|
||||||
rows += 1
|
rows += 1
|
||||||
|
|
||||||
@@ -658,7 +658,8 @@ class AutocompleteWidget(object):
|
|||||||
orderby=None, limitby=(0, 10), distinct=False,
|
orderby=None, limitby=(0, 10), distinct=False,
|
||||||
keyword='_autocomplete_%(tablename)s_%(fieldname)s',
|
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, default_var='ac'):
|
at_beginning=True, default_var='ac', user_signature=True,
|
||||||
|
hash_vars=False):
|
||||||
|
|
||||||
self.help_fields = help_fields or []
|
self.help_fields = help_fields or []
|
||||||
self.help_string = help_string
|
self.help_string = help_string
|
||||||
@@ -681,12 +682,14 @@ class AutocompleteWidget(object):
|
|||||||
else:
|
else:
|
||||||
self.is_reference = False
|
self.is_reference = False
|
||||||
if hasattr(request, 'application'):
|
if hasattr(request, 'application'):
|
||||||
urlvars = request.vars
|
urlvars = copy.copy(request.vars)
|
||||||
urlvars[default_var] = 1
|
urlvars[default_var] = 1
|
||||||
self.url = URL(args=request.args, vars=urlvars)
|
self.url = URL(args=request.args, vars=urlvars,
|
||||||
self.callback()
|
user_signature=user_signature, hash_vars=hash_vars)
|
||||||
|
self.run_callback = True
|
||||||
else:
|
else:
|
||||||
self.url = request
|
self.url = request
|
||||||
|
self.run_callback = False
|
||||||
|
|
||||||
def callback(self):
|
def callback(self):
|
||||||
if self.keyword in self.request.vars:
|
if self.keyword in self.request.vars:
|
||||||
@@ -759,6 +762,8 @@ class AutocompleteWidget(object):
|
|||||||
raise HTTP(200, '')
|
raise HTTP(200, '')
|
||||||
|
|
||||||
def __call__(self, field, value, **attributes):
|
def __call__(self, field, value, **attributes):
|
||||||
|
if self.run_callback:
|
||||||
|
self.callback()
|
||||||
default = dict(
|
default = dict(
|
||||||
_type='text',
|
_type='text',
|
||||||
value=(value is not None and str(value)) or '',
|
value=(value is not None and str(value)) or '',
|
||||||
@@ -1082,6 +1087,106 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
|
|||||||
return parent
|
return parent
|
||||||
return _inner
|
return _inner
|
||||||
|
|
||||||
|
# bootstrap 4
|
||||||
|
def formstyle_bootstrap4_stacked(form, fields):
|
||||||
|
""" bootstrap 3 format form layout
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Experimental!
|
||||||
|
"""
|
||||||
|
parent = CAT()
|
||||||
|
for id, label, controls, help in fields:
|
||||||
|
# wrappers
|
||||||
|
_help = SPAN(help, _class='help-block')
|
||||||
|
# embed _help into _controls
|
||||||
|
_controls = CAT(controls, _help)
|
||||||
|
if isinstance(controls, INPUT):
|
||||||
|
if controls['_type'] == 'submit':
|
||||||
|
controls.add_class('btn btn-primary')
|
||||||
|
if controls['_type'] == 'button':
|
||||||
|
controls.add_class('btn btn-secondary')
|
||||||
|
elif controls['_type'] == 'file':
|
||||||
|
controls.add_class('input-file')
|
||||||
|
elif controls['_type'] in ('text', 'password'):
|
||||||
|
controls.add_class('form-control')
|
||||||
|
elif controls['_type'] == 'checkbox':
|
||||||
|
label['_for'] = None
|
||||||
|
label.insert(0, controls)
|
||||||
|
label.insert(0, ' ')
|
||||||
|
_controls = DIV(label, _help, _class="checkbox")
|
||||||
|
label = ''
|
||||||
|
elif isinstance(controls, (SELECT, TEXTAREA)):
|
||||||
|
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')
|
||||||
|
|
||||||
|
elif isinstance(controls, CAT) and isinstance(controls[0], INPUT):
|
||||||
|
controls[0].add_class('form-control')
|
||||||
|
|
||||||
|
if isinstance(label, LABEL):
|
||||||
|
label['_class'] = add_class(label.get('_class'), 'form-control-label')
|
||||||
|
|
||||||
|
parent.append(DIV(label, _controls, _class='form-group', _id=id))
|
||||||
|
return parent
|
||||||
|
|
||||||
|
|
||||||
|
def formstyle_bootstrap4_inline_factory(col_label_size=3):
|
||||||
|
""" bootstrap 3 horizontal form layout
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Experimental!
|
||||||
|
"""
|
||||||
|
def _inner(form, fields):
|
||||||
|
form.add_class('form-horizontal')
|
||||||
|
label_col_class = "col-sm-%d" % col_label_size
|
||||||
|
col_class = "col-sm-%d" % (12 - col_label_size)
|
||||||
|
offset_class = "col-sm-offset-%d" % col_label_size
|
||||||
|
parent = CAT()
|
||||||
|
for id, label, controls, help in fields:
|
||||||
|
# wrappers
|
||||||
|
_help = SPAN(help, _class='help-block')
|
||||||
|
# embed _help into _controls
|
||||||
|
_controls = DIV(controls, _help, _class="%s" % (col_class))
|
||||||
|
if isinstance(controls, INPUT):
|
||||||
|
if controls['_type'] == 'submit':
|
||||||
|
controls.add_class('btn btn-primary')
|
||||||
|
_controls = DIV(controls, _class="%s %s" % (col_class, offset_class))
|
||||||
|
if controls['_type'] == 'button':
|
||||||
|
controls.add_class('btn btn-secondary')
|
||||||
|
elif controls['_type'] == 'file':
|
||||||
|
controls.add_class('input-file')
|
||||||
|
elif controls['_type'] in ('text', 'password'):
|
||||||
|
controls.add_class('form-control')
|
||||||
|
elif controls['_type'] == 'checkbox':
|
||||||
|
label['_for'] = None
|
||||||
|
label.insert(0, controls)
|
||||||
|
label.insert(1, ' ')
|
||||||
|
_controls = DIV(DIV(label, _help, _class="checkbox"),
|
||||||
|
_class="%s %s" % (offset_class, col_class))
|
||||||
|
label = ''
|
||||||
|
elif isinstance(controls, (SELECT, TEXTAREA)):
|
||||||
|
controls.add_class('form-control')
|
||||||
|
|
||||||
|
elif isinstance(controls, SPAN):
|
||||||
|
_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 isinstance(controls, CAT) and isinstance(controls[0], INPUT):
|
||||||
|
controls[0].add_class('form-control')
|
||||||
|
if isinstance(label, LABEL):
|
||||||
|
label['_class'] = add_class(label.get('_class'), 'form-control-label %s' % label_col_class)
|
||||||
|
|
||||||
|
parent.append(DIV(label, _controls, _class='form-group row', _id=id))
|
||||||
|
return parent
|
||||||
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
class SQLFORM(FORM):
|
class SQLFORM(FORM):
|
||||||
|
|
||||||
@@ -1170,6 +1275,8 @@ class SQLFORM(FORM):
|
|||||||
bootstrap=formstyle_bootstrap,
|
bootstrap=formstyle_bootstrap,
|
||||||
bootstrap3_stacked=formstyle_bootstrap3_stacked,
|
bootstrap3_stacked=formstyle_bootstrap3_stacked,
|
||||||
bootstrap3_inline=formstyle_bootstrap3_inline_factory(3),
|
bootstrap3_inline=formstyle_bootstrap3_inline_factory(3),
|
||||||
|
bootstrap4_stacked=formstyle_bootstrap4_stacked,
|
||||||
|
bootstrap4_inline=formstyle_bootstrap4_inline_factory(3),
|
||||||
inline=formstyle_inline,
|
inline=formstyle_inline,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1237,9 +1344,16 @@ class SQLFORM(FORM):
|
|||||||
# if no fields are provided, build it from the provided table
|
# if no fields are provided, build it from the provided table
|
||||||
# will only use writable or readable fields, unless forced to ignore
|
# will only use writable or readable fields, unless forced to ignore
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = [f.name for f in table if
|
if not readonly:
|
||||||
(ignore_rw or f.writable or f.readable) and
|
if not record:
|
||||||
(readonly or not f.compute)]
|
# create form should only show writable fields
|
||||||
|
fields = [f.name for f in table if (ignore_rw or f.writable) and not f.compute]
|
||||||
|
else:
|
||||||
|
# update form should also show readable fields and computed fields (but in reaodnly mode)
|
||||||
|
fields = [f.name for f in table if (ignore_rw or f.writable or f.readable)]
|
||||||
|
else:
|
||||||
|
# read only form should show all readable fields
|
||||||
|
fields = [f.name for f in table if (ignore_rw or f.readable)]
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
|
|
||||||
# make sure we have an id
|
# make sure we have an id
|
||||||
@@ -1322,7 +1436,7 @@ class SQLFORM(FORM):
|
|||||||
label = LABEL(label, label and sep, _for=field_id,
|
label = LABEL(label, label and sep, _for=field_id,
|
||||||
_id=field_id + SQLFORM.ID_LABEL_SUFFIX)
|
_id=field_id + SQLFORM.ID_LABEL_SUFFIX)
|
||||||
|
|
||||||
cond = readonly or \
|
cond = readonly or field.compute or \
|
||||||
(not ignore_rw and not field.writable and field.readable)
|
(not ignore_rw and not field.writable and field.readable)
|
||||||
|
|
||||||
if cond:
|
if cond:
|
||||||
@@ -1568,6 +1682,7 @@ class SQLFORM(FORM):
|
|||||||
keepvalues = True if self.record else False
|
keepvalues = True if self.record else False
|
||||||
|
|
||||||
if self.readonly:
|
if self.readonly:
|
||||||
|
self.deleted = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if request_vars.__class__.__name__ == 'Request':
|
if request_vars.__class__.__name__ == 'Request':
|
||||||
@@ -1724,7 +1839,7 @@ class SQLFORM(FORM):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
field = self.table[fieldname]
|
field = self.table[fieldname]
|
||||||
if field.type == 'id':
|
if field.type == 'id' or field.compute:
|
||||||
continue
|
continue
|
||||||
if field.type == 'boolean':
|
if field.type == 'boolean':
|
||||||
if self.vars.get(fieldname, False):
|
if self.vars.get(fieldname, False):
|
||||||
@@ -1916,8 +2031,10 @@ class SQLFORM(FORM):
|
|||||||
if 'table_name' in attributes:
|
if 'table_name' in attributes:
|
||||||
del attributes['table_name']
|
del attributes['table_name']
|
||||||
|
|
||||||
return SQLFORM(DAL(None).define_table(table_name, *fields),
|
# Clone fields, while passing tables straight through
|
||||||
**attributes)
|
fields_with_clones = [f.clone() if isinstance(f, Field) else f for f in fields]
|
||||||
|
|
||||||
|
return SQLFORM(DAL(None).define_table(table_name, *fields_with_clones), **attributes)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_query(fields, keywords):
|
def build_query(fields, keywords):
|
||||||
@@ -1932,11 +2049,13 @@ class SQLFORM(FORM):
|
|||||||
if settings.global_settings.web2py_runtime_gae:
|
if settings.global_settings.web2py_runtime_gae:
|
||||||
return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields])
|
return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields])
|
||||||
else:
|
else:
|
||||||
|
if not (sfields and key and key.split()):
|
||||||
|
return fields[0].table
|
||||||
return reduce(lambda a,b:a&b,[
|
return reduce(lambda a,b:a&b,[
|
||||||
reduce(lambda a,b: a|b, [
|
reduce(lambda a,b: a|b, [
|
||||||
field.contains(k) for field in sfields]
|
field.contains(k) for field in sfields]
|
||||||
) for k in key.split()])
|
) for k in key.split()])
|
||||||
|
|
||||||
# from https://groups.google.com/forum/#!topic/web2py/hKe6lI25Bv4
|
# from https://groups.google.com/forum/#!topic/web2py/hKe6lI25Bv4
|
||||||
# needs testing...
|
# needs testing...
|
||||||
#words = key.split(' ') if key else []
|
#words = key.split(' ') if key else []
|
||||||
@@ -2156,6 +2275,7 @@ class SQLFORM(FORM):
|
|||||||
represent_none=None,
|
represent_none=None,
|
||||||
showblobs=False):
|
showblobs=False):
|
||||||
|
|
||||||
|
dbset = None
|
||||||
formstyle = formstyle or current.response.formstyle
|
formstyle = formstyle or current.response.formstyle
|
||||||
if isinstance(query, Set):
|
if isinstance(query, Set):
|
||||||
query = query.query
|
query = query.query
|
||||||
@@ -2187,7 +2307,7 @@ class SQLFORM(FORM):
|
|||||||
cornerall='',
|
cornerall='',
|
||||||
cornertop='',
|
cornertop='',
|
||||||
cornerbottom='',
|
cornerbottom='',
|
||||||
button='button btn btn-default',
|
button='button btn btn-default btn-secondary',
|
||||||
buttontext='buttontext button',
|
buttontext='buttontext button',
|
||||||
buttonadd='icon plus icon-plus glyphicon glyphicon-plus',
|
buttonadd='icon plus icon-plus glyphicon glyphicon-plus',
|
||||||
buttonback='icon leftarrow icon-arrow-left glyphicon glyphicon-arrow-left',
|
buttonback='icon leftarrow icon-arrow-left glyphicon glyphicon-arrow-left',
|
||||||
@@ -2337,12 +2457,12 @@ class SQLFORM(FORM):
|
|||||||
for k, f in iteritems(table):
|
for k, f in iteritems(table):
|
||||||
if isinstance(f, Field.Virtual):
|
if isinstance(f, Field.Virtual):
|
||||||
f.tablename = table._tablename
|
f.tablename = table._tablename
|
||||||
columns = [f for f in fields if f.tablename in tablenames]
|
columns = [f for f in fields if f.tablename in tablenames and f.listable]
|
||||||
else:
|
else:
|
||||||
fields = []
|
fields = []
|
||||||
columns = []
|
columns = []
|
||||||
filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs)
|
filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs)
|
||||||
filter2 = lambda f: isinstance(f, Field) and f.readable
|
filter2 = lambda f: isinstance(f, Field) and f.readable and f.listable
|
||||||
for table in tables:
|
for table in tables:
|
||||||
fields += filter(filter1, table)
|
fields += filter(filter1, table)
|
||||||
columns += filter(filter2, table)
|
columns += filter(filter2, table)
|
||||||
@@ -2560,8 +2680,8 @@ class SQLFORM(FORM):
|
|||||||
try:
|
try:
|
||||||
# the query should be constructed using searchable
|
# the query should be constructed using searchable
|
||||||
# fields but not virtual fields
|
# fields but not virtual fields
|
||||||
sfields = reduce(lambda a, b: a + b,
|
is_searchable = lambda f: f.readable and not isinstance(f, Field.Virtual) and f.searchable
|
||||||
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
|
sfields = reduce(lambda a, b: a + b, [filter(is_searchable, t) for t in tables])
|
||||||
# use custom_query using searchable
|
# use custom_query using searchable
|
||||||
if callable(searchable):
|
if callable(searchable):
|
||||||
dbset = dbset(searchable(sfields, keywords))
|
dbset = dbset(searchable(sfields, keywords))
|
||||||
@@ -2628,8 +2748,8 @@ class SQLFORM(FORM):
|
|||||||
_id=skeywords_id, _class='form-control',
|
_id=skeywords_id, _class='form-control',
|
||||||
_onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else ''
|
_onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else ''
|
||||||
),
|
),
|
||||||
INPUT(_type='submit', _value=T('Search'), _class="btn btn-default"),
|
INPUT(_type='submit', _value=T('Search'), _class="btn btn-default btn-secondary"),
|
||||||
INPUT(_type='submit', _value=T('Clear'), _class="btn btn-default",
|
INPUT(_type='submit', _value=T('Clear'), _class="btn btn-default btn-secondary",
|
||||||
_onclick="jQuery('#%s').val('');" % skeywords_id),
|
_onclick="jQuery('#%s').val('');" % skeywords_id),
|
||||||
*hidden_fields,
|
*hidden_fields,
|
||||||
_method="GET", _action=url), search_menu)
|
_method="GET", _action=url), search_menu)
|
||||||
@@ -3015,7 +3135,7 @@ class SQLFORM(FORM):
|
|||||||
order=request.vars.order or '',
|
order=request.vars.order or '',
|
||||||
_export_type=k,
|
_export_type=k,
|
||||||
keywords=keywords or ''))
|
keywords=keywords or ''))
|
||||||
export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default'))
|
export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default btn-secondary'))
|
||||||
export_menu = \
|
export_menu = \
|
||||||
DIV(T('Export:'), _class="w2p_export_menu", *export_links)
|
DIV(T('Export:'), _class="w2p_export_menu", *export_links)
|
||||||
else:
|
else:
|
||||||
@@ -3034,6 +3154,7 @@ class SQLFORM(FORM):
|
|||||||
res.view_form = view_form
|
res.view_form = view_form
|
||||||
res.search_form = search_form
|
res.search_form = search_form
|
||||||
res.rows = rows
|
res.rows = rows
|
||||||
|
res.dbset = dbset
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -3152,8 +3273,8 @@ class SQLFORM(FORM):
|
|||||||
# if isinstance(linked_tables, dict):
|
# if isinstance(linked_tables, dict):
|
||||||
# linked_tables = linked_tables.get(table._tablename, [])
|
# linked_tables = linked_tables.get(table._tablename, [])
|
||||||
if linked_tables is None or referee in linked_tables:
|
if linked_tables is None or referee in linked_tables:
|
||||||
field.represent = (lambda id, r=None, referee=referee, rep=field.represent:
|
field.represent = (lambda id, r=None, referee=referee, rep=field.represent:
|
||||||
A(callable(rep) and rep(id) or id,
|
A(callable(rep) and rep(id) or id,
|
||||||
cid=request.cid, _href=url(args=['view', referee, id])))
|
cid=request.cid, _href=url(args=['view', referee, id])))
|
||||||
except (KeyError, ValueError, TypeError):
|
except (KeyError, ValueError, TypeError):
|
||||||
redirect(URL(args=table._tablename))
|
redirect(URL(args=table._tablename))
|
||||||
@@ -3316,7 +3437,7 @@ class SQLTABLE(TABLE):
|
|||||||
return
|
return
|
||||||
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
|
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
|
||||||
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields))
|
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields))
|
||||||
tablemap = dict(((f.tablename, f.table) for f in fieldmap.values()))
|
tablemap = dict(((f.tablename, f.table) if isinstance(f, Field) else (f._table._tablename, f._table) for f in fieldmap.values()))
|
||||||
for table in tablemap.values():
|
for table in tablemap.values():
|
||||||
pref = table._tablename + '.'
|
pref = table._tablename + '.'
|
||||||
fieldmap.update(((pref+f.name, f) for f in table._virtual_fields))
|
fieldmap.update(((pref+f.name, f) for f in table._virtual_fields))
|
||||||
@@ -3428,7 +3549,7 @@ class SQLTABLE(TABLE):
|
|||||||
if ref.find('.') >= 0:
|
if ref.find('.') >= 0:
|
||||||
tref, fref = ref.split('.')
|
tref, fref = ref.split('.')
|
||||||
if hasattr(sqlrows.db[tref], '_primarykey'):
|
if hasattr(sqlrows.db[tref], '_primarykey'):
|
||||||
href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref: r}))
|
href = '%s/%s?%s' % (linkto, tref, urlencode({fref: r}))
|
||||||
r = A(represent(field, r, record), _href=str(href))
|
r = A(represent(field, r, record), _href=str(href))
|
||||||
elif field.represent:
|
elif field.represent:
|
||||||
if field not in repr_cache:
|
if field not in repr_cache:
|
||||||
@@ -3439,7 +3560,7 @@ class SQLTABLE(TABLE):
|
|||||||
elif linkto and hasattr(field._table, '_primarykey')\
|
elif linkto and hasattr(field._table, '_primarykey')\
|
||||||
and fieldname in field._table._primarykey:
|
and fieldname in field._table._primarykey:
|
||||||
# have to test this with multi-key tables
|
# have to test this with multi-key tables
|
||||||
key = urllib.urlencode(dict([
|
key = urlencode(dict([
|
||||||
((tablename in record
|
((tablename in record
|
||||||
and isinstance(record, Row)
|
and isinstance(record, Row)
|
||||||
and isinstance(record[tablename], Row)) and
|
and isinstance(record[tablename], Row)) and
|
||||||
@@ -3559,7 +3680,9 @@ class ExportClass(object):
|
|||||||
if not self.rows.db._adapter.REGEX_TABLE_DOT_FIELD.match(col):
|
if not self.rows.db._adapter.REGEX_TABLE_DOT_FIELD.match(col):
|
||||||
row.append(record._extra[col])
|
row.append(record._extra[col])
|
||||||
else:
|
else:
|
||||||
(t, f) = col.split('.')
|
# The grid code modifies rows.colnames, adding double quotes
|
||||||
|
# around the table and field names -- so they must be removed here.
|
||||||
|
(t, f) = [name.strip('"') for name in col.split('.')]
|
||||||
field = self.rows.db[t][f]
|
field = self.rows.db[t][f]
|
||||||
if isinstance(record.get(t, None), (Row, dict)):
|
if isinstance(record.get(t, None), (Row, dict)):
|
||||||
value = record[t][f]
|
value = record[t][f]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from gluon import fileutils
|
|||||||
from gluon.dal import DAL, Field, Table
|
from gluon.dal import DAL, Field, Table
|
||||||
from gluon.http import HTTP
|
from gluon.http import HTTP
|
||||||
from gluon.fileutils import open_file
|
from gluon.fileutils import open_file
|
||||||
|
from gluon.cache import CacheInRam
|
||||||
|
|
||||||
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||||
|
|
||||||
@@ -104,6 +105,21 @@ class TestAppAdmin(unittest.TestCase):
|
|||||||
self._test_index()
|
self._test_index()
|
||||||
remove_compiled_application(appname_path)
|
remove_compiled_application(appname_path)
|
||||||
|
|
||||||
|
def test_index_minify(self):
|
||||||
|
# test for gluon/contrib/minify
|
||||||
|
self.env['response'].optimize_css = 'concat|minify'
|
||||||
|
self.env['response'].optimize_js = 'concat|minify'
|
||||||
|
self.env['current'].cache = Storage({'ram':CacheInRam()})
|
||||||
|
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
|
||||||
|
self._test_index()
|
||||||
|
file_l = os.listdir(os.path.join(appname_path, 'static', 'temp'))
|
||||||
|
file_l.sort()
|
||||||
|
self.assertTrue(len(file_l) == 2)
|
||||||
|
self.assertEqual(file_l[0][0:10], 'compressed')
|
||||||
|
self.assertEqual(file_l[1][0:10], 'compressed')
|
||||||
|
self.assertEqual(file_l[0][-3:], 'css')
|
||||||
|
self.assertEqual(file_l[1][-2:], 'js')
|
||||||
|
|
||||||
def test_select(self):
|
def test_select(self):
|
||||||
request = self.env['request']
|
request = self.env['request']
|
||||||
request.args = List(['db'])
|
request.args = List(['db'])
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ class TestAuthAPI(unittest.TestCase):
|
|||||||
self.assertTrue('new_password2' in result['errors'])
|
self.assertTrue('new_password2' in result['errors'])
|
||||||
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
|
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
|
||||||
self.assertTrue('old_password' in result['errors'])
|
self.assertTrue('old_password' in result['errors'])
|
||||||
|
# Test the default 4 min_length is enforced on change password
|
||||||
|
result = self.auth.change_password(old_password='1234', new_password='123', new_password2='123')
|
||||||
|
self.assertTrue('new_password' in result['errors'])
|
||||||
|
|
||||||
def test_verify_key(self):
|
def test_verify_key(self):
|
||||||
self.auth.settings.registration_requires_verification = True
|
self.auth.settings.registration_requires_verification = True
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from gluon.fileutils import parse_version
|
from gluon.fileutils import parse_version, fix_newlines
|
||||||
|
|
||||||
|
|
||||||
class TestFileUtils(unittest.TestCase):
|
class TestFileUtils(unittest.TestCase):
|
||||||
@@ -22,3 +23,6 @@ class TestFileUtils(unittest.TestCase):
|
|||||||
# Semantic Beta
|
# Semantic Beta
|
||||||
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26')
|
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26')
|
||||||
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
|
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
|
||||||
|
|
||||||
|
def test_fix_newlines(self):
|
||||||
|
fix_newlines(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
@@ -158,10 +158,10 @@ class testResponse(unittest.TestCase):
|
|||||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||||
content = return_includes(response)
|
content = return_includes(response)
|
||||||
self.assertEqual(content,
|
self.assertEqual(content,
|
||||||
|
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>' +
|
||||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
'<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" />' +
|
'<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="/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 = Response()
|
||||||
@@ -230,3 +230,14 @@ class testResponse(unittest.TestCase):
|
|||||||
current.session._fixup_before_save()
|
current.session._fixup_before_save()
|
||||||
cookie = str(current.response.cookies)
|
cookie = str(current.response.cookies)
|
||||||
self.assertTrue('httponly' not in cookie.lower())
|
self.assertTrue('httponly' not in cookie.lower())
|
||||||
|
|
||||||
|
def test_include_meta(self):
|
||||||
|
response = Response()
|
||||||
|
response.meta[u'web2py'] = 'web2py'
|
||||||
|
response.include_meta()
|
||||||
|
self.assertEqual(response.body.getvalue(), '\n<meta name="web2py" content="web2py" />\n')
|
||||||
|
response = Response()
|
||||||
|
response.meta[u'meta_dict'] = {u'tag_name':'tag_value'}
|
||||||
|
response.include_meta()
|
||||||
|
self.assertEqual(response.body.getvalue(), '\n<meta tag_name="tag_value" />\n')
|
||||||
|
|
||||||
|
|||||||
@@ -340,18 +340,20 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
|
|
||||||
def test_SCRIPT(self):
|
def test_SCRIPT(self):
|
||||||
self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(),
|
self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(),
|
||||||
'''<script a="1" b="2"><!--
|
b'''<script a="1" b="2"><!--
|
||||||
<>
|
<>
|
||||||
//--></script>''')
|
//--></script>''')
|
||||||
self.assertEqual(SCRIPT('<>').xml(),
|
self.assertEqual(SCRIPT('<>').xml(),
|
||||||
'''<script><!--
|
b'''<script><!--
|
||||||
<>
|
<>
|
||||||
//--></script>''')
|
//--></script>''')
|
||||||
self.assertEqual(SCRIPT().xml(), b'<script></script>')
|
self.assertEqual(SCRIPT().xml(), b'<script></script>')
|
||||||
|
self.assertEqual(SCRIPT(';').xml() + DIV().xml(),
|
||||||
|
b'<script><!--\n;\n//--></script><div></div>')
|
||||||
|
|
||||||
def test_STYLE(self):
|
def test_STYLE(self):
|
||||||
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
|
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
|
||||||
'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
|
b'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
|
||||||
# Try to hit : return DIV.xml(self)
|
# Try to hit : return DIV.xml(self)
|
||||||
self.assertEqual(STYLE().xml(), b'<style></style>')
|
self.assertEqual(STYLE().xml(), b'<style></style>')
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import tempfile
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from gluon import languages
|
from gluon import languages
|
||||||
from gluon._compat import PY2
|
from gluon._compat import PY2, to_unicode, to_bytes
|
||||||
|
from gluon.storage import Messages
|
||||||
|
from gluon.html import SPAN
|
||||||
|
|
||||||
MP_WORKING = 0
|
MP_WORKING = 0
|
||||||
try:
|
try:
|
||||||
@@ -108,6 +110,8 @@ class TestTranslations(unittest.TestCase):
|
|||||||
T.force('it')
|
T.force('it')
|
||||||
self.assertEqual(str(T('Hello World')),
|
self.assertEqual(str(T('Hello World')),
|
||||||
'Salve Mondo')
|
'Salve Mondo')
|
||||||
|
self.assertEqual(to_unicode(T('Hello World')),
|
||||||
|
'Salve Mondo')
|
||||||
|
|
||||||
class TestDummyApp(unittest.TestCase):
|
class TestDummyApp(unittest.TestCase):
|
||||||
|
|
||||||
@@ -179,3 +183,43 @@ def index():
|
|||||||
for key in ['hello', 'world', '%s %%{shop}', 'ahoy']:
|
for key in ['hello', 'world', '%s %%{shop}', 'ahoy']:
|
||||||
self.assertTrue(key in en_dict)
|
self.assertTrue(key in en_dict)
|
||||||
self.assertTrue(key in pt_dict)
|
self.assertTrue(key in pt_dict)
|
||||||
|
|
||||||
|
class TestMessages(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if os.path.isdir('gluon'):
|
||||||
|
self.langpath = 'applications/welcome/languages'
|
||||||
|
else:
|
||||||
|
self.langpath = os.path.realpath(
|
||||||
|
'../../applications/welcome/languages')
|
||||||
|
self.http_accept_language = 'en'
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_decode(self):
|
||||||
|
T = languages.translator(self.langpath, self.http_accept_language)
|
||||||
|
messages = Messages(T)
|
||||||
|
messages.update({'email_sent':'Email sent', 'test': "ä"})
|
||||||
|
self.assertEqual(to_unicode(messages.email_sent, 'utf-8'), 'Email sent')
|
||||||
|
|
||||||
|
class TestHTMLTag(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if os.path.isdir('gluon'):
|
||||||
|
self.langpath = 'applications/welcome/languages'
|
||||||
|
else:
|
||||||
|
self.langpath = os.path.realpath(
|
||||||
|
'../../applications/welcome/languages')
|
||||||
|
self.http_accept_language = 'en'
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_decode(self):
|
||||||
|
T = languages.translator(self.langpath, self.http_accept_language)
|
||||||
|
elem = SPAN(T("Complete"))
|
||||||
|
self.assertEqual(elem.flatten(), "Complete")
|
||||||
|
elem = SPAN(T("Cannot be empty", language="ru"))
|
||||||
|
self.assertEqual(elem.xml(), to_bytes('<span>Пустое значение недопустимо</span>'))
|
||||||
|
self.assertEqual(elem.flatten(), 'Пустое значение недопустимо')
|
||||||
|
|||||||
+12
-11
File diff suppressed because one or more lines are too long
@@ -278,8 +278,68 @@ class TestValidators(unittest.TestCase):
|
|||||||
self.assertEqual(rtn, ('jerry', 'oops'))
|
self.assertEqual(rtn, ('jerry', 'oops'))
|
||||||
rtn = IS_IN_DB(db, 'person.id', '%(name)s', auto_add=True)('jerry')
|
rtn = IS_IN_DB(db, 'person.id', '%(name)s', auto_add=True)('jerry')
|
||||||
self.assertEqual(rtn, (3, None))
|
self.assertEqual(rtn, (3, None))
|
||||||
|
# Test it works with reference table
|
||||||
|
db.define_table('ref_table',
|
||||||
|
Field('name'),
|
||||||
|
Field('person_id', 'reference person')
|
||||||
|
)
|
||||||
|
ret = db.ref_table.validate_and_insert(name='test reference table')
|
||||||
|
self.assertFalse(list(ret.errors))
|
||||||
|
ret = db.ref_table.validate_and_insert(name='test reference table', person_id=george_id)
|
||||||
|
self.assertFalse(list(ret.errors))
|
||||||
|
rtn = IS_IN_DB(db, 'ref_table.person_id', '%(name)s')(george_id)
|
||||||
|
self.assertEqual(rtn, (george_id, None))
|
||||||
|
# Test it works with reference table.field and keyed table
|
||||||
|
db.define_table('person_keyed',
|
||||||
|
Field('name'),
|
||||||
|
primarykey=['name'])
|
||||||
|
db.person_keyed.insert(name='george')
|
||||||
|
db.person_keyed.insert(name='costanza')
|
||||||
|
rtn = IS_IN_DB(db, 'person_keyed.name')('george')
|
||||||
|
self.assertEqual(rtn, ('george', None))
|
||||||
|
db.define_table('ref_table_field',
|
||||||
|
Field('name'),
|
||||||
|
Field('person_name', 'reference person_keyed.name')
|
||||||
|
)
|
||||||
|
ret = db.ref_table_field.validate_and_insert(name='test reference table.field')
|
||||||
|
self.assertFalse(list(ret.errors))
|
||||||
|
ret = db.ref_table_field.validate_and_insert(name='test reference table.field', person_name='george')
|
||||||
|
self.assertFalse(list(ret.errors))
|
||||||
|
vldtr = IS_IN_DB(db, 'ref_table_field.person_name', '%(name)s')
|
||||||
|
vldtr.options()
|
||||||
|
rtn = vldtr('george')
|
||||||
|
self.assertEqual(rtn, ('george', None))
|
||||||
|
# Test it works with list:reference table
|
||||||
|
db.define_table('list_ref_table',
|
||||||
|
Field('name'),
|
||||||
|
Field('person_list', 'list:reference person'))
|
||||||
|
ret = db.list_ref_table.validate_and_insert(name='test list:reference table')
|
||||||
|
self.assertFalse(list(ret.errors))
|
||||||
|
ret = db.list_ref_table.validate_and_insert(name='test list:reference table', person_list=[george_id,costanza_id])
|
||||||
|
self.assertFalse(list(ret.errors))
|
||||||
|
vldtr = IS_IN_DB(db, 'list_ref_table.person_list')
|
||||||
|
vldtr.options()
|
||||||
|
rtn = vldtr([george_id,costanza_id])
|
||||||
|
self.assertEqual(rtn, ([george_id,costanza_id], None))
|
||||||
|
# Test it works with list:reference table.field and keyed table
|
||||||
|
#db.define_table('list_ref_table_field',
|
||||||
|
# Field('name'),
|
||||||
|
# Field('person_list', 'list:reference person_keyed.name'))
|
||||||
|
#ret = db.list_ref_table_field.validate_and_insert(name='test list:reference table.field')
|
||||||
|
#self.assertFalse(list(ret.errors))
|
||||||
|
#ret = db.list_ref_table_field.validate_and_insert(name='test list:reference table.field', person_list=['george','costanza'])
|
||||||
|
#self.assertFalse(list(ret.errors))
|
||||||
|
#vldtr = IS_IN_DB(db, 'list_ref_table_field.person_list')
|
||||||
|
#vldtr.options()
|
||||||
|
#rtn = vldtr(['george','costanza'])
|
||||||
|
#self.assertEqual(rtn, (['george','costanza'], None))
|
||||||
db.person.drop()
|
db.person.drop()
|
||||||
db.category.drop()
|
db.category.drop()
|
||||||
|
db.person_keyed.drop()
|
||||||
|
db.ref_table.drop()
|
||||||
|
db.ref_table_field.drop()
|
||||||
|
db.list_ref_table.drop()
|
||||||
|
#db.list_ref_table_field.drop()
|
||||||
|
|
||||||
def test_IS_NOT_IN_DB(self):
|
def test_IS_NOT_IN_DB(self):
|
||||||
from gluon.dal import DAL, Field
|
from gluon.dal import DAL, Field
|
||||||
@@ -444,6 +504,8 @@ class TestValidators(unittest.TestCase):
|
|||||||
self.assertEqual(rtn, (None, 'Enter a value'))
|
self.assertEqual(rtn, (None, 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()('')
|
rtn = IS_NOT_EMPTY()('')
|
||||||
self.assertEqual(rtn, ('', 'Enter a value'))
|
self.assertEqual(rtn, ('', 'Enter a value'))
|
||||||
|
rtn = IS_NOT_EMPTY()(b'')
|
||||||
|
self.assertEqual(rtn, (b'', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()(' ')
|
rtn = IS_NOT_EMPTY()(' ')
|
||||||
self.assertEqual(rtn, (' ', 'Enter a value'))
|
self.assertEqual(rtn, (' ', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()(' \n\t')
|
rtn = IS_NOT_EMPTY()(' \n\t')
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ class TestWeb(LiveTest):
|
|||||||
password='test',
|
password='test',
|
||||||
_formname='login')
|
_formname='login')
|
||||||
client.post('user/login', data=data)
|
client.post('user/login', data=data)
|
||||||
self.assertTrue('Welcome Homer' in client.text)
|
self.assertTrue('Homer' in client.text)
|
||||||
|
|
||||||
# check registration and login were successful
|
# check registration and login were successful
|
||||||
client.get('index')
|
client.get('index')
|
||||||
|
|
||||||
self.assertTrue('Welcome Homer' in client.text)
|
self.assertTrue('Homer' in client.text)
|
||||||
|
|
||||||
client = WebClient('http://127.0.0.1:8000/admin/default/')
|
client = WebClient('http://127.0.0.1:8000/admin/default/')
|
||||||
client.post('index', data=dict(password='testpass'))
|
client.post('index', data=dict(password='testpass'))
|
||||||
|
|||||||
+92
-71
@@ -12,7 +12,7 @@ Auth, Mail, PluginManager and various utilities
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from gluon._compat import pickle, thread, urllib2, Cookie, StringIO
|
from gluon._compat import pickle, thread, urllib2, Cookie, StringIO, urlencode
|
||||||
from gluon._compat import configparser, MIMEBase, MIMEMultipart, MIMEText, Header
|
from gluon._compat import configparser, MIMEBase, MIMEMultipart, MIMEText, Header
|
||||||
from gluon._compat import Encoders, Charset, long, urllib_quote, iteritems
|
from gluon._compat import Encoders, Charset, long, urllib_quote, iteritems
|
||||||
from gluon._compat import to_bytes, to_native, add_charset
|
from gluon._compat import to_bytes, to_native, add_charset
|
||||||
@@ -27,7 +27,6 @@ import time
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import traceback
|
import traceback
|
||||||
import smtplib
|
import smtplib
|
||||||
import urllib
|
|
||||||
import email.utils
|
import email.utils
|
||||||
import random
|
import random
|
||||||
import hmac
|
import hmac
|
||||||
@@ -873,7 +872,7 @@ class Recaptcha(DIV):
|
|||||||
and len(recaptcha_challenge_field)):
|
and len(recaptcha_challenge_field)):
|
||||||
self.errors['captcha'] = self.error_message
|
self.errors['captcha'] = self.error_message
|
||||||
return False
|
return False
|
||||||
params = urllib.urlencode({
|
params = urlencode({
|
||||||
'privatekey': private_key,
|
'privatekey': private_key,
|
||||||
'remoteip': remoteip,
|
'remoteip': remoteip,
|
||||||
'challenge': recaptcha_challenge_field,
|
'challenge': recaptcha_challenge_field,
|
||||||
@@ -1026,7 +1025,7 @@ class Recaptcha2(DIV):
|
|||||||
if not recaptcha_response_field:
|
if not recaptcha_response_field:
|
||||||
self.errors['captcha'] = self.error_message
|
self.errors['captcha'] = self.error_message
|
||||||
return False
|
return False
|
||||||
params = urllib.urlencode({
|
params = urlencode({
|
||||||
'secret': self.private_key,
|
'secret': self.private_key,
|
||||||
'remoteip': remoteip,
|
'remoteip': remoteip,
|
||||||
'response': recaptcha_response_field,
|
'response': recaptcha_response_field,
|
||||||
@@ -1127,7 +1126,6 @@ def addrow(form, a, b, c, style, _id, position=-1):
|
|||||||
|
|
||||||
|
|
||||||
class AuthJWT(object):
|
class AuthJWT(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Experimental!
|
Experimental!
|
||||||
|
|
||||||
@@ -1321,7 +1319,7 @@ class AuthJWT(object):
|
|||||||
# is the following safe or should we use
|
# is the following safe or should we use
|
||||||
# calendar.timegm(datetime.datetime.utcnow().timetuple())
|
# calendar.timegm(datetime.datetime.utcnow().timetuple())
|
||||||
# result seem to be the same (seconds since epoch, in UTC)
|
# result seem to be the same (seconds since epoch, in UTC)
|
||||||
now = time.mktime(datetime.datetime.now().timetuple())
|
now = time.mktime(datetime.datetime.utcnow().timetuple())
|
||||||
expires = now + self.expiration
|
expires = now + self.expiration
|
||||||
payload = dict(
|
payload = dict(
|
||||||
hmac_key=session_auth['hmac_key'],
|
hmac_key=session_auth['hmac_key'],
|
||||||
@@ -1333,7 +1331,7 @@ class AuthJWT(object):
|
|||||||
return payload
|
return payload
|
||||||
|
|
||||||
def refresh_token(self, orig_payload):
|
def refresh_token(self, orig_payload):
|
||||||
now = time.mktime(datetime.datetime.now().timetuple())
|
now = time.mktime(datetime.datetime.utcnow().timetuple())
|
||||||
if self.verify_expiration:
|
if self.verify_expiration:
|
||||||
orig_exp = orig_payload['exp']
|
orig_exp = orig_payload['exp']
|
||||||
if orig_exp + self.leeway < now:
|
if orig_exp + self.leeway < now:
|
||||||
@@ -1537,7 +1535,8 @@ class Auth(AuthAPI):
|
|||||||
# ## these are messages that can be customized
|
# ## these are messages that can be customized
|
||||||
default_messages = dict(AuthAPI.default_messages,
|
default_messages = dict(AuthAPI.default_messages,
|
||||||
access_denied='Insufficient privileges',
|
access_denied='Insufficient privileges',
|
||||||
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete the process',
|
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete '
|
||||||
|
'the process',
|
||||||
bulk_invite_subject='Invitation to join %(site)s',
|
bulk_invite_subject='Invitation to join %(site)s',
|
||||||
delete_label='Check to delete',
|
delete_label='Check to delete',
|
||||||
email_sent='Email sent',
|
email_sent='Email sent',
|
||||||
@@ -1762,7 +1761,7 @@ class Auth(AuthAPI):
|
|||||||
if auth.last_visit and auth.last_visit + delta > now:
|
if auth.last_visit and auth.last_visit + delta > now:
|
||||||
self.user = auth.user
|
self.user = auth.user
|
||||||
# this is a trick to speed up sessions to avoid many writes
|
# this is a trick to speed up sessions to avoid many writes
|
||||||
if (now - auth.last_visit).seconds > (auth.expiration / 10):
|
if (now - auth.last_visit).seconds > (auth.expiration // 10):
|
||||||
auth.last_visit = now
|
auth.last_visit = now
|
||||||
else:
|
else:
|
||||||
self.user = None
|
self.user = None
|
||||||
@@ -1792,6 +1791,7 @@ class Auth(AuthAPI):
|
|||||||
servicevalidate='serviceValidate',
|
servicevalidate='serviceValidate',
|
||||||
proxyvalidate='proxyValidate',
|
proxyvalidate='proxyValidate',
|
||||||
logout='logout'),
|
logout='logout'),
|
||||||
|
cas_create_user=True,
|
||||||
extra_fields={},
|
extra_fields={},
|
||||||
actions_disabled=[],
|
actions_disabled=[],
|
||||||
controller=controller,
|
controller=controller,
|
||||||
@@ -1840,14 +1840,15 @@ class Auth(AuthAPI):
|
|||||||
# ## these are messages that can be customized
|
# ## these are messages that can be customized
|
||||||
messages = self.messages = Messages(current.T)
|
messages = self.messages = Messages(current.T)
|
||||||
messages.update(Auth.default_messages)
|
messages.update(Auth.default_messages)
|
||||||
messages.update(ajax_failed_authentication=DIV(H4('NOT AUTHORIZED'),
|
messages.update(ajax_failed_authentication=
|
||||||
'Please ',
|
DIV(H4('NOT AUTHORIZED'),
|
||||||
A('login',
|
'Please ',
|
||||||
_href=self.settings.login_url +
|
A('login',
|
||||||
('?_next=' + urllib_quote(current.request.env.http_web2py_component_location))
|
_href=self.settings.login_url +
|
||||||
if current.request.env.http_web2py_component_location else ''),
|
('?_next=' + urllib_quote(current.request.env.http_web2py_component_location))
|
||||||
' to view this content.',
|
if current.request.env.http_web2py_component_location else ''),
|
||||||
_class='not-authorized alert alert-block'))
|
' to view this content.',
|
||||||
|
_class='not-authorized alert alert-block'))
|
||||||
messages.lock_keys = True
|
messages.lock_keys = True
|
||||||
|
|
||||||
# for "remember me" option
|
# for "remember me" option
|
||||||
@@ -1876,7 +1877,7 @@ class Auth(AuthAPI):
|
|||||||
# _next variable in the request.
|
# _next variable in the request.
|
||||||
if next:
|
if next:
|
||||||
parts = next.split('/')
|
parts = next.split('/')
|
||||||
if not ':' in parts[0]:
|
if ':' not in parts[0]:
|
||||||
return next
|
return next
|
||||||
elif len(parts) > 2 and parts[0].endswith(':') and parts[1:3] == ['', host]:
|
elif len(parts) > 2 and parts[0].endswith(':') and parts[1:3] == ['', host]:
|
||||||
return next
|
return next
|
||||||
@@ -2008,8 +2009,7 @@ class Auth(AuthAPI):
|
|||||||
items.append({'name': T('Lost password?'),
|
items.append({'name': T('Lost password?'),
|
||||||
'href': href('request_reset_password'),
|
'href': href('request_reset_password'),
|
||||||
'icon': 'icon-lock'})
|
'icon': 'icon-lock'})
|
||||||
if (self.settings.use_username and not
|
if self.settings.use_username and 'retrieve_username' not in self.settings.actions_disabled:
|
||||||
'retrieve_username' in self.settings.actions_disabled):
|
|
||||||
items.append({'name': T('Forgot username?'),
|
items.append({'name': T('Forgot username?'),
|
||||||
'href': href('retrieve_username'),
|
'href': href('retrieve_username'),
|
||||||
'icon': 'icon-edit'})
|
'icon': 'icon-edit'})
|
||||||
@@ -2181,9 +2181,7 @@ class Auth(AuthAPI):
|
|||||||
current_record.replace('_', ' ').title())
|
current_record.replace('_', ' ').title())
|
||||||
for table in tables:
|
for table in tables:
|
||||||
fieldnames = table.fields()
|
fieldnames = table.fields()
|
||||||
if ('id' in fieldnames and
|
if 'id' in fieldnames and 'modified_on' in fieldnames and current_record not in fieldnames:
|
||||||
'modified_on' in fieldnames and
|
|
||||||
not current_record in fieldnames):
|
|
||||||
table._enable_record_versioning(archive_db=archive_db,
|
table._enable_record_versioning(archive_db=archive_db,
|
||||||
archive_name=archive_names,
|
archive_name=archive_names,
|
||||||
current_record=current_record,
|
current_record=current_record,
|
||||||
@@ -2213,7 +2211,8 @@ class Auth(AuthAPI):
|
|||||||
fake_migrate = db._fake_migrate
|
fake_migrate = db._fake_migrate
|
||||||
settings = self.settings
|
settings = self.settings
|
||||||
settings.enable_tokens = enable_tokens
|
settings.enable_tokens = enable_tokens
|
||||||
signature_list = super(Auth, self).define_tables(username, signature, migrate, fake_migrate)._table_signature_list
|
signature_list = \
|
||||||
|
super(Auth, self).define_tables(username, signature, migrate, fake_migrate)._table_signature_list
|
||||||
|
|
||||||
now = current.request.now
|
now = current.request.now
|
||||||
reference_table_user = 'reference %s' % settings.table_user_name
|
reference_table_user = 'reference %s' % settings.table_user_name
|
||||||
@@ -2285,6 +2284,7 @@ class Auth(AuthAPI):
|
|||||||
If the user doesn't yet exist, then they are created.
|
If the user doesn't yet exist, then they are created.
|
||||||
"""
|
"""
|
||||||
table_user = self.table_user()
|
table_user = self.table_user()
|
||||||
|
create_user = self.settings.cas_create_user
|
||||||
user = None
|
user = None
|
||||||
checks = []
|
checks = []
|
||||||
# make a guess about who this user is
|
# make a guess about who this user is
|
||||||
@@ -2317,6 +2317,11 @@ class Auth(AuthAPI):
|
|||||||
update_keys[key] = keys[key]
|
update_keys[key] = keys[key]
|
||||||
user.update_record(**update_keys)
|
user.update_record(**update_keys)
|
||||||
elif checks:
|
elif checks:
|
||||||
|
if create_user is False:
|
||||||
|
# Remove current open session a send message
|
||||||
|
self.logout(next=None, onlogout=None, log=None)
|
||||||
|
raise HTTP(403, "Forbidden. User need to be created first.")
|
||||||
|
|
||||||
if 'first_name' not in keys and 'first_name' in table_user.fields:
|
if 'first_name' not in keys and 'first_name' in table_user.fields:
|
||||||
guess = keys.get('email', 'anonymous').split('@')[0]
|
guess = keys.get('email', 'anonymous').split('@')[0]
|
||||||
keys['first_name'] = keys.get('username', guess)
|
keys['first_name'] = keys.get('username', guess)
|
||||||
@@ -2360,7 +2365,7 @@ class Auth(AuthAPI):
|
|||||||
if callable(basic_auth_realm):
|
if callable(basic_auth_realm):
|
||||||
basic_auth_realm = basic_auth_realm()
|
basic_auth_realm = basic_auth_realm()
|
||||||
elif isinstance(basic_auth_realm, (unicode, str)):
|
elif isinstance(basic_auth_realm, (unicode, str)):
|
||||||
basic_realm = unicode(basic_auth_realm)
|
basic_realm = unicode(basic_auth_realm) # Warning python 3.5 does not have method unicod
|
||||||
elif basic_auth_realm is True:
|
elif basic_auth_realm is True:
|
||||||
basic_realm = u'' + current.request.application
|
basic_realm = u'' + current.request.application
|
||||||
http_401 = HTTP(401, u'Not Authorized', **{'WWW-Authenticate': u'Basic realm="' + basic_realm + '"'})
|
http_401 = HTTP(401, u'Not Authorized', **{'WWW-Authenticate': u'Basic realm="' + basic_realm + '"'})
|
||||||
@@ -2462,7 +2467,7 @@ class Auth(AuthAPI):
|
|||||||
_href=service + query_sep + "ticket=" + ticket)
|
_href=service + query_sep + "ticket=" + ticket)
|
||||||
else:
|
else:
|
||||||
redirect(service + query_sep + "ticket=" + ticket)
|
redirect(service + query_sep + "ticket=" + ticket)
|
||||||
if self.is_logged_in() and not 'renew' in request.vars:
|
if self.is_logged_in() and 'renew' not in request.vars:
|
||||||
return allow_access()
|
return allow_access()
|
||||||
elif not self.is_logged_in() and 'gateway' in request.vars:
|
elif not self.is_logged_in() and 'gateway' in request.vars:
|
||||||
redirect(session._cas_service)
|
redirect(session._cas_service)
|
||||||
@@ -2492,9 +2497,9 @@ class Auth(AuthAPI):
|
|||||||
success = True
|
success = True
|
||||||
|
|
||||||
def build_response(body):
|
def build_response(body):
|
||||||
return '<?xml version="1.0" encoding="UTF-8"?>\n' +\
|
xml_body = to_native(TAG['cas:serviceResponse'](
|
||||||
TAG['cas:serviceResponse'](
|
body, **{'_xmlns:cas': 'http://www.yale.edu/tp/cas'}).xml())
|
||||||
body, **{'_xmlns:cas': 'http://www.yale.edu/tp/cas'}).xml()
|
return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml_body
|
||||||
if success:
|
if success:
|
||||||
if version == 1:
|
if version == 1:
|
||||||
message = 'yes\n%s' % user[userfield]
|
message = 'yes\n%s' % user[userfield]
|
||||||
@@ -2779,7 +2784,7 @@ class Auth(AuthAPI):
|
|||||||
# If auth.settings.auth_two_factor_enabled it will enable two factor
|
# If auth.settings.auth_two_factor_enabled it will enable two factor
|
||||||
# for all the app. Another way to anble two factor is that the user
|
# for all the app. Another way to anble two factor is that the user
|
||||||
# must be part of a group that is called auth.settings.two_factor_authentication_group
|
# must be part of a group that is called auth.settings.two_factor_authentication_group
|
||||||
if user and self.settings.auth_two_factor_enabled == True:
|
if user and self.settings.auth_two_factor_enabled is True:
|
||||||
session.auth_two_factor_enabled = True
|
session.auth_two_factor_enabled = True
|
||||||
elif user and self.settings.two_factor_authentication_group:
|
elif user and self.settings.two_factor_authentication_group:
|
||||||
role = self.settings.two_factor_authentication_group
|
role = self.settings.two_factor_authentication_group
|
||||||
@@ -2809,7 +2814,7 @@ class Auth(AuthAPI):
|
|||||||
# Set the way we generate the code or we send the code. For example using SMS...
|
# Set the way we generate the code or we send the code. For example using SMS...
|
||||||
two_factor_methods = self.settings.two_factor_methods
|
two_factor_methods = self.settings.two_factor_methods
|
||||||
|
|
||||||
if two_factor_methods == []:
|
if not two_factor_methods:
|
||||||
# TODO: Add some error checking to handle cases where email cannot be sent
|
# TODO: Add some error checking to handle cases where email cannot be sent
|
||||||
self.settings.mailer.send(
|
self.settings.mailer.send(
|
||||||
to=user.email,
|
to=user.email,
|
||||||
@@ -2832,47 +2837,49 @@ class Auth(AuthAPI):
|
|||||||
hideerror=settings.hideerror):
|
hideerror=settings.hideerror):
|
||||||
accepted_form = True
|
accepted_form = True
|
||||||
|
|
||||||
'''
|
"""
|
||||||
The lists is executed after form validation for each of the corresponding action.
|
The lists is executed after form validation for each of the corresponding action.
|
||||||
For example, in your model:
|
For example, in your model:
|
||||||
|
|
||||||
In your models copy and paste:
|
In your models copy and paste:
|
||||||
|
|
||||||
#Before define tables, we add some extra field to auth_user
|
# Before define tables, we add some extra field to auth_user
|
||||||
auth.settings.extra_fields['auth_user'] = [
|
auth.settings.extra_fields['auth_user'] = [
|
||||||
Field('motp_secret', 'password', length=512, default='', label='MOTP Secret'),
|
Field('motp_secret', 'password', length=512, default='', label='MOTP Secret'),
|
||||||
Field('motp_pin', 'string', length=128, default='', label='MOTP PIN')]
|
Field('motp_pin', 'string', length=128, default='', label='MOTP PIN')]
|
||||||
|
|
||||||
OFFSET = 60 #Be sure is the same in your OTP Client
|
OFFSET = 60 # Be sure is the same in your OTP Client
|
||||||
|
|
||||||
#Set session.auth_two_factor to None. Because the code is generated by external app.
|
# Set session.auth_two_factor to None. Because the code is generated by external app.
|
||||||
# This will avoid to use the default setting and send a code by email.
|
# This will avoid to use the default setting and send a code by email.
|
||||||
def _set_two_factor(user, auth_two_factor):
|
def _set_two_factor(user, auth_two_factor):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def verify_otp(user, otp):
|
def verify_otp(user, otp):
|
||||||
import time
|
import time
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
epoch_time = int(time.time())
|
epoch_time = int(time.time())
|
||||||
time_start = int(str(epoch_time - OFFSET)[:-1])
|
time_start = int(str(epoch_time - OFFSET)[:-1])
|
||||||
time_end = int(str(epoch_time + OFFSET)[:-1])
|
time_end = int(str(epoch_time + OFFSET)[:-1])
|
||||||
for t in range(time_start - 1, time_end + 1):
|
for t in range(time_start - 1, time_end + 1):
|
||||||
to_hash = str(t) + user.motp_secret + user.motp_pin
|
to_hash = str(t) + user.motp_secret + user.motp_pin
|
||||||
hash = md5(to_hash).hexdigest()[:6]
|
hash = md5(to_hash).hexdigest()[:6]
|
||||||
if otp == hash:
|
if otp == hash:
|
||||||
return hash
|
return hash
|
||||||
|
|
||||||
auth.settings.auth_two_factor_enabled = True
|
auth.settings.auth_two_factor_enabled = True
|
||||||
auth.messages.two_factor_comment = "Verify your OTP Client for the code."
|
auth.messages.two_factor_comment = "Verify your OTP Client for the code."
|
||||||
auth.settings.two_factor_methods = [lambda user, auth_two_factor: _set_two_factor(user, auth_two_factor)]
|
auth.settings.two_factor_methods = [lambda user,
|
||||||
|
auth_two_factor: _set_two_factor(user, auth_two_factor)]
|
||||||
auth.settings.two_factor_onvalidation = [lambda user, otp: verify_otp(user, otp)]
|
auth.settings.two_factor_onvalidation = [lambda user, otp: verify_otp(user, otp)]
|
||||||
|
|
||||||
'''
|
"""
|
||||||
if self.settings.two_factor_onvalidation != []:
|
if self.settings.two_factor_onvalidation:
|
||||||
|
|
||||||
for two_factor_onvalidation in self.settings.two_factor_onvalidation:
|
for two_factor_onvalidation in self.settings.two_factor_onvalidation:
|
||||||
try:
|
try:
|
||||||
session.auth_two_factor = two_factor_onvalidation(session.auth_two_factor_user, form.vars['authentication_code'])
|
session.auth_two_factor = \
|
||||||
|
two_factor_onvalidation(session.auth_two_factor_user, form.vars['authentication_code'])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -3043,7 +3050,7 @@ class Auth(AuthAPI):
|
|||||||
|
|
||||||
if self.settings.register_verify_password:
|
if self.settings.register_verify_password:
|
||||||
if self.settings.register_fields is None:
|
if self.settings.register_fields is None:
|
||||||
self.settings.register_fields = [f.name for f in table_user if f.writable]
|
self.settings.register_fields = [f.name for f in table_user if f.writable and not f.compute]
|
||||||
k = self.settings.register_fields.index(passfield)
|
k = self.settings.register_fields.index(passfield)
|
||||||
self.settings.register_fields.insert(k + 1, "password_two")
|
self.settings.register_fields.insert(k + 1, "password_two")
|
||||||
extra_fields = [
|
extra_fields = [
|
||||||
@@ -3655,6 +3662,16 @@ class Auth(AuthAPI):
|
|||||||
if not self.is_logged_in():
|
if not self.is_logged_in():
|
||||||
redirect(self.settings.login_url,
|
redirect(self.settings.login_url,
|
||||||
client_side=self.settings.client_side)
|
client_side=self.settings.client_side)
|
||||||
|
|
||||||
|
# Go to external link to change the password
|
||||||
|
if self.settings.login_form != self:
|
||||||
|
cas = self.settings.login_form
|
||||||
|
# To prevent error if change_password_url function is not defined in alternate login
|
||||||
|
if hasattr(cas, 'change_password_url'):
|
||||||
|
next = cas.change_password_url(next)
|
||||||
|
if next is not None:
|
||||||
|
redirect(next)
|
||||||
|
|
||||||
db = self.db
|
db = self.db
|
||||||
table_user = self.table_user()
|
table_user = self.table_user()
|
||||||
s = db(table_user.id == self.user.id)
|
s = db(table_user.id == self.user.id)
|
||||||
@@ -3675,7 +3692,8 @@ class Auth(AuthAPI):
|
|||||||
requires = [requires]
|
requires = [requires]
|
||||||
requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
|
requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
|
||||||
if requires:
|
if requires:
|
||||||
requires[0].min_length = 0
|
requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
|
||||||
|
requires[0].min_length = 0 # But do not enforce minimum length for the old password
|
||||||
form = SQLFORM.factory(
|
form = SQLFORM.factory(
|
||||||
Field('old_password', 'password', requires=requires,
|
Field('old_password', 'password', requires=requires,
|
||||||
label=self.messages.old_password),
|
label=self.messages.old_password),
|
||||||
@@ -3759,9 +3777,9 @@ class Auth(AuthAPI):
|
|||||||
if any(f.compute for f in extra_fields):
|
if any(f.compute for f in extra_fields):
|
||||||
user = table_user[self.user.id]
|
user = table_user[self.user.id]
|
||||||
self._update_session_user(user)
|
self._update_session_user(user)
|
||||||
|
self.update_groups()
|
||||||
else:
|
else:
|
||||||
self.user.update(table_user._filter_fields(form.vars))
|
self.user.update(table_user._filter_fields(form.vars))
|
||||||
|
|
||||||
session.flash = self.messages.profile_updated
|
session.flash = self.messages.profile_updated
|
||||||
self.log_event(log, self.user)
|
self.log_event(log, self.user)
|
||||||
callback(onaccept, form)
|
callback(onaccept, form)
|
||||||
@@ -4146,7 +4164,7 @@ class Auth(AuthAPI):
|
|||||||
archive_table = table._db[archive_table_name]
|
archive_table = table._db[archive_table_name]
|
||||||
new_record = {current_record: form.vars.id}
|
new_record = {current_record: form.vars.id}
|
||||||
for fieldname in archive_table.fields:
|
for fieldname in archive_table.fields:
|
||||||
if not fieldname in ['id', current_record]:
|
if fieldname not in ['id', current_record]:
|
||||||
if archive_current and fieldname in form.vars:
|
if archive_current and fieldname in form.vars:
|
||||||
new_record[fieldname] = form.vars[fieldname]
|
new_record[fieldname] = form.vars[fieldname]
|
||||||
elif form.record and fieldname in form.record:
|
elif form.record and fieldname in form.record:
|
||||||
@@ -4719,7 +4737,7 @@ def fetch(url, data=None, headers=None,
|
|||||||
user_agent='Mozilla/5.0'):
|
user_agent='Mozilla/5.0'):
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
if data is not None:
|
if data is not None:
|
||||||
data = urllib.urlencode(data)
|
data = urlencode(data)
|
||||||
if user_agent:
|
if user_agent:
|
||||||
headers['User-agent'] = user_agent
|
headers['User-agent'] = user_agent
|
||||||
headers['Cookie'] = ' '.join(
|
headers['Cookie'] = ' '.join(
|
||||||
@@ -4956,7 +4974,10 @@ class Service(object):
|
|||||||
|
|
||||||
Then call it with:
|
Then call it with:
|
||||||
|
|
||||||
wget --post-data '{"jsonrpc": "2.0", "id": 1, "method": "myfunction", "params": {"a": 1, "b": 2}}' http://..../app/default/call/jsonrpc2
|
wget --post-data '{"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "myfunction",
|
||||||
|
"params": {"a": 1, "b": 2}}' http://..../app/default/call/jsonrpc2
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.jsonrpc2_procedures[f.__name__] = f
|
self.jsonrpc2_procedures[f.__name__] = f
|
||||||
@@ -5527,15 +5548,15 @@ def prettydate(d, T=lambda x: x, utc=False):
|
|||||||
else:
|
else:
|
||||||
suffix = ' ago'
|
suffix = ' ago'
|
||||||
if dt.days >= 2 * 365:
|
if dt.days >= 2 * 365:
|
||||||
return T('%d years' + suffix) % int(dt.days / 365)
|
return T('%d years' + suffix) % int(dt.days // 365)
|
||||||
elif dt.days >= 365:
|
elif dt.days >= 365:
|
||||||
return T('1 year' + suffix)
|
return T('1 year' + suffix)
|
||||||
elif dt.days >= 60:
|
elif dt.days >= 60:
|
||||||
return T('%d months' + suffix) % int(dt.days / 30)
|
return T('%d months' + suffix) % int(dt.days // 30)
|
||||||
elif dt.days >= 27: # 4 weeks ugly
|
elif dt.days >= 27: # 4 weeks ugly
|
||||||
return T('1 month' + suffix)
|
return T('1 month' + suffix)
|
||||||
elif dt.days >= 14:
|
elif dt.days >= 14:
|
||||||
return T('%d weeks' + suffix) % int(dt.days / 7)
|
return T('%d weeks' + suffix) % int(dt.days // 7)
|
||||||
elif dt.days >= 7:
|
elif dt.days >= 7:
|
||||||
return T('1 week' + suffix)
|
return T('1 week' + suffix)
|
||||||
elif dt.days > 1:
|
elif dt.days > 1:
|
||||||
@@ -5543,11 +5564,11 @@ def prettydate(d, T=lambda x: x, utc=False):
|
|||||||
elif dt.days == 1:
|
elif dt.days == 1:
|
||||||
return T('1 day' + suffix)
|
return T('1 day' + suffix)
|
||||||
elif dt.seconds >= 2 * 60 * 60:
|
elif dt.seconds >= 2 * 60 * 60:
|
||||||
return T('%d hours' + suffix) % int(dt.seconds / 3600)
|
return T('%d hours' + suffix) % int(dt.seconds // 3600)
|
||||||
elif dt.seconds >= 60 * 60:
|
elif dt.seconds >= 60 * 60:
|
||||||
return T('1 hour' + suffix)
|
return T('1 hour' + suffix)
|
||||||
elif dt.seconds >= 2 * 60:
|
elif dt.seconds >= 2 * 60:
|
||||||
return T('%d minutes' + suffix) % int(dt.seconds / 60)
|
return T('%d minutes' + suffix) % int(dt.seconds // 60)
|
||||||
elif dt.seconds >= 60:
|
elif dt.seconds >= 60:
|
||||||
return T('1 minute' + suffix)
|
return T('1 minute' + suffix)
|
||||||
elif dt.seconds > 1:
|
elif dt.seconds > 1:
|
||||||
@@ -5600,12 +5621,12 @@ class PluginManager(object):
|
|||||||
|
|
||||||
where the plugin is used::
|
where the plugin is used::
|
||||||
|
|
||||||
>>> print plugins.me.param1
|
>>> print(plugins.me.param1)
|
||||||
3
|
3
|
||||||
>>> print plugins.me.param2
|
>>> print(plugins.me.param2)
|
||||||
6
|
6
|
||||||
>>> plugins.me.param3 = 8
|
>>> plugins.me.param3 = 8
|
||||||
>>> print plugins.me.param3
|
>>> print(plugins.me.param3)
|
||||||
8
|
8
|
||||||
|
|
||||||
Here are some tests::
|
Here are some tests::
|
||||||
@@ -5613,25 +5634,25 @@ class PluginManager(object):
|
|||||||
>>> a=PluginManager()
|
>>> a=PluginManager()
|
||||||
>>> a.x=6
|
>>> a.x=6
|
||||||
>>> b=PluginManager('check')
|
>>> b=PluginManager('check')
|
||||||
>>> print b.x
|
>>> print(b.x)
|
||||||
6
|
6
|
||||||
>>> b=PluginManager() # reset settings
|
>>> b=PluginManager() # reset settings
|
||||||
>>> print b.x
|
>>> print(b.x)
|
||||||
<Storage {}>
|
<Storage {}>
|
||||||
>>> b.x=7
|
>>> b.x=7
|
||||||
>>> print a.x
|
>>> print(a.x)
|
||||||
7
|
7
|
||||||
>>> a.y.z=8
|
>>> a.y.z=8
|
||||||
>>> print b.y.z
|
>>> print(b.y.z)
|
||||||
8
|
8
|
||||||
>>> test_thread_separation()
|
>>> test_thread_separation()
|
||||||
5
|
5
|
||||||
>>> plugins=PluginManager('me',db='mydb')
|
>>> plugins=PluginManager('me',db='mydb')
|
||||||
>>> print plugins.me.db
|
>>> print(plugins.me.db)
|
||||||
mydb
|
mydb
|
||||||
>>> print 'me' in plugins
|
>>> print('me' in plugins)
|
||||||
True
|
True
|
||||||
>>> print plugins.me.installed
|
>>> print(plugins.me.installed)
|
||||||
True
|
True
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -5780,7 +5801,7 @@ class Expose(object):
|
|||||||
return os.path.realpath(f)
|
return os.path.realpath(f)
|
||||||
|
|
||||||
def issymlink_out(self, f):
|
def issymlink_out(self, f):
|
||||||
"True if f is a symlink and is pointing outside of self.base"
|
"""True if f is a symlink and is pointing outside of self.base"""
|
||||||
return os.path.islink(f) and not self.in_base(f)
|
return os.path.islink(f) and not self.in_base(f)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ Utilities and class for UTF8 strings managing
|
|||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from gluon._compat import builtin as __builtin__, unicodeT, iteritems, to_unicode, to_native
|
from gluon._compat import builtin as __builtin__, unicodeT, iteritems, to_unicode, to_native, reload
|
||||||
|
|
||||||
__all__ = ['Utf8']
|
__all__ = ['Utf8']
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -156,12 +156,12 @@ def get_digest(value):
|
|||||||
raise ValueError("Invalid digest algorithm: %s" % value)
|
raise ValueError("Invalid digest algorithm: %s" % value)
|
||||||
|
|
||||||
DIGEST_ALG_BY_SIZE = {
|
DIGEST_ALG_BY_SIZE = {
|
||||||
128 / 4: 'md5',
|
128 // 4: 'md5',
|
||||||
160 / 4: 'sha1',
|
160 // 4: 'sha1',
|
||||||
224 / 4: 'sha224',
|
224 // 4: 'sha224',
|
||||||
256 / 4: 'sha256',
|
256 // 4: 'sha256',
|
||||||
384 / 4: 'sha384',
|
384 // 4: 'sha384',
|
||||||
512 / 4: 'sha512',
|
512 // 4: 'sha512',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+278
-154
@@ -21,7 +21,8 @@ import struct
|
|||||||
import decimal
|
import decimal
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, to_unicode, to_native, string_types, urlparse
|
from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, \
|
||||||
|
to_unicode, to_native, string_types, urlparse
|
||||||
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
||||||
from pydal.objects import Field, FieldVirtual, FieldMethod
|
from pydal.objects import Field, FieldVirtual, FieldMethod
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
@@ -452,10 +453,10 @@ class IS_IN_SET(Validator):
|
|||||||
if not self.labels:
|
if not self.labels:
|
||||||
items = [(k, k) for (i, k) in enumerate(self.theset)]
|
items = [(k, k) for (i, k) in enumerate(self.theset)]
|
||||||
else:
|
else:
|
||||||
items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
|
items = [(k, list(self.labels)[i]) for (i, k) in enumerate(self.theset)]
|
||||||
if self.sort:
|
if self.sort:
|
||||||
items.sort(key=lambda o: str(o[1]).upper())
|
items.sort(key=lambda o: str(o[1]).upper())
|
||||||
if zero and not self.zero is None and not self.multiple:
|
if zero and self.zero is not None and not self.multiple:
|
||||||
items.insert(0, ('', self.zero))
|
items.insert(0, ('', self.zero))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@@ -823,7 +824,7 @@ class IS_INT_IN_RANGE(Validator):
|
|||||||
|
|
||||||
def str2dec(number):
|
def str2dec(number):
|
||||||
s = str(number)
|
s = str(number)
|
||||||
if not '.' in s:
|
if '.' not in s:
|
||||||
s += '.00'
|
s += '.00'
|
||||||
else:
|
else:
|
||||||
s += '0' * (2 - len(s.split('.')[1]))
|
s += '0' * (2 - len(s.split('.')[1]))
|
||||||
@@ -995,7 +996,7 @@ def is_empty(value, empty_regex=None):
|
|||||||
value = value.strip()
|
value = value.strip()
|
||||||
if empty_regex is not None and empty_regex.match(value):
|
if empty_regex is not None and empty_regex.match(value):
|
||||||
value = ''
|
value = ''
|
||||||
if value is None or value == '' or value == []:
|
if value is None or value == '' or value == b'' or value == []:
|
||||||
return (_value, True)
|
return (_value, True)
|
||||||
return (_value, False)
|
return (_value, False)
|
||||||
|
|
||||||
@@ -1213,7 +1214,7 @@ class IS_EMAIL(Validator):
|
|||||||
domain_encoded = to_unicode(domain).encode('idna').decode('ascii')
|
domain_encoded = to_unicode(domain).encode('idna').decode('ascii')
|
||||||
match_domain = self.domain_regex.match(domain_encoded)
|
match_domain = self.domain_regex.match(domain_encoded)
|
||||||
|
|
||||||
match = (match_body != None) and (match_domain != None)
|
match = (match_body is not None) and (match_domain is not None)
|
||||||
except (TypeError, UnicodeError):
|
except (TypeError, UnicodeError):
|
||||||
# Value may not be a string where we can look for matches.
|
# Value may not be a string where we can look for matches.
|
||||||
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
|
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
|
||||||
@@ -1247,7 +1248,7 @@ class IS_LIST_OF_EMAILS(object):
|
|||||||
f = IS_EMAIL()
|
f = IS_EMAIL()
|
||||||
for email in self.split_emails.findall(value):
|
for email in self.split_emails.findall(value):
|
||||||
error = f(email)[1]
|
error = f(email)[1]
|
||||||
if error and not email in bad_emails:
|
if error and email not in bad_emails:
|
||||||
bad_emails.append(email)
|
bad_emails.append(email)
|
||||||
if not bad_emails:
|
if not bad_emails:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
@@ -1461,9 +1462,9 @@ def unicode_to_ascii_authority(authority):
|
|||||||
if label:
|
if label:
|
||||||
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
|
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
|
||||||
else:
|
else:
|
||||||
# encodings.idna.ToASCII does not accept an empty string, but
|
# encodings.idna.ToASCII does not accept an empty string, but
|
||||||
# it is necessary for us to allow for empty labels so that we
|
# it is necessary for us to allow for empty labels so that we
|
||||||
# don't modify the URL
|
# don't modify the URL
|
||||||
asciiLabels.append('')
|
asciiLabels.append('')
|
||||||
# RFC 3490, Section 4, Step 5
|
# RFC 3490, Section 4, Step 5
|
||||||
return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
|
return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
|
||||||
@@ -1527,13 +1528,17 @@ def unicode_to_ascii_url(url, prepend_scheme):
|
|||||||
if prepended:
|
if prepended:
|
||||||
scheme = ''
|
scheme = ''
|
||||||
|
|
||||||
unparsed = urlparse.urlunparse((scheme, unicode_to_ascii_authority(authority), escape_unicode(path), '', escape_unicode(query), str(fragment)))
|
unparsed = urlparse.urlunparse((scheme,
|
||||||
|
unicode_to_ascii_authority(authority),
|
||||||
|
escape_unicode(path),
|
||||||
|
'',
|
||||||
|
escape_unicode(query),
|
||||||
|
str(fragment)))
|
||||||
if unparsed.startswith('//'):
|
if unparsed.startswith('//'):
|
||||||
unparsed = unparsed[2:] # Remove the // urlunparse puts in the beginning
|
unparsed = unparsed[2:] # Remove the // urlunparse puts in the beginning
|
||||||
return unparsed
|
return unparsed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IS_GENERIC_URL(Validator):
|
class IS_GENERIC_URL(Validator):
|
||||||
"""
|
"""
|
||||||
Rejects a URL string if any of the following is true:
|
Rejects a URL string if any of the following is true:
|
||||||
@@ -1651,173 +1656,292 @@ class IS_GENERIC_URL(Validator):
|
|||||||
# else the URL is not valid
|
# else the URL is not valid
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
|
|
||||||
# Sources (obtained 2015-Feb-24):
|
# Sources (obtained 2017-Nov-11):
|
||||||
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||||
# see scripts/parse_top_level_domains.py for an easy update
|
# see scripts/parse_top_level_domains.py for an easy update
|
||||||
|
|
||||||
official_top_level_domains = [
|
official_top_level_domains = [
|
||||||
# a
|
# a
|
||||||
'abogado', 'ac', 'academy', 'accountants', 'active', 'actor',
|
'aaa', 'aarp', 'abarth', 'abb', 'abbott', 'abbvie', 'abc',
|
||||||
'ad', 'adult', 'ae', 'aero', 'af', 'ag', 'agency', 'ai',
|
'able', 'abogado', 'abudhabi', 'ac', 'academy', 'accenture',
|
||||||
'airforce', 'al', 'allfinanz', 'alsace', 'am', 'amsterdam', 'an',
|
'accountant', 'accountants', 'aco', 'active', 'actor', 'ad',
|
||||||
'android', 'ao', 'apartments', 'aq', 'aquarelle', 'ar', 'archi',
|
'adac', 'ads', 'adult', 'ae', 'aeg', 'aero', 'aetna', 'af',
|
||||||
'army', 'arpa', 'as', 'asia', 'associates', 'at', 'attorney',
|
'afamilycompany', 'afl', 'africa', 'ag', 'agakhan', 'agency',
|
||||||
'au', 'auction', 'audio', 'autos', 'aw', 'ax', 'axa', 'az',
|
'ai', 'aig', 'aigo', 'airbus', 'airforce', 'airtel', 'akdn',
|
||||||
|
'al', 'alfaromeo', 'alibaba', 'alipay', 'allfinanz', 'allstate',
|
||||||
|
'ally', 'alsace', 'alstom', 'am', 'americanexpress',
|
||||||
|
'americanfamily', 'amex', 'amfam', 'amica', 'amsterdam',
|
||||||
|
'analytics', 'android', 'anquan', 'anz', 'ao', 'aol',
|
||||||
|
'apartments', 'app', 'apple', 'aq', 'aquarelle', 'ar', 'arab',
|
||||||
|
'aramco', 'archi', 'army', 'arpa', 'art', 'arte', 'as', 'asda',
|
||||||
|
'asia', 'associates', 'at', 'athleta', 'attorney', 'au',
|
||||||
|
'auction', 'audi', 'audible', 'audio', 'auspost', 'author',
|
||||||
|
'auto', 'autos', 'avianca', 'aw', 'aws', 'ax', 'axa', 'az',
|
||||||
|
'azure',
|
||||||
# b
|
# b
|
||||||
'ba', 'band', 'bank', 'bar', 'barclaycard', 'barclays',
|
'ba', 'baby', 'baidu', 'banamex', 'bananarepublic', 'band',
|
||||||
'bargains', 'bayern', 'bb', 'bd', 'be', 'beer', 'berlin', 'best',
|
'bank', 'bar', 'barcelona', 'barclaycard', 'barclays',
|
||||||
'bf', 'bg', 'bh', 'bi', 'bid', 'bike', 'bingo', 'bio', 'biz',
|
'barefoot', 'bargains', 'baseball', 'basketball', 'bauhaus',
|
||||||
'bj', 'black', 'blackfriday', 'bloomberg', 'blue', 'bm', 'bmw',
|
'bayern', 'bb', 'bbc', 'bbt', 'bbva', 'bcg', 'bcn', 'bd', 'be',
|
||||||
'bn', 'bnpparibas', 'bo', 'boo', 'boutique', 'br', 'brussels',
|
'beats', 'beauty', 'beer', 'bentley', 'berlin', 'best',
|
||||||
'bs', 'bt', 'budapest', 'build', 'builders', 'business', 'buzz',
|
'bestbuy', 'bet', 'bf', 'bg', 'bh', 'bharti', 'bi', 'bible',
|
||||||
'bv', 'bw', 'by', 'bz', 'bzh',
|
'bid', 'bike', 'bing', 'bingo', 'bio', 'biz', 'bj', 'black',
|
||||||
|
'blackfriday', 'blanco', 'blockbuster', 'blog', 'bloomberg',
|
||||||
|
'blue', 'bm', 'bms', 'bmw', 'bn', 'bnl', 'bnpparibas', 'bo',
|
||||||
|
'boats', 'boehringer', 'bofa', 'bom', 'bond', 'boo', 'book',
|
||||||
|
'booking', 'boots', 'bosch', 'bostik', 'boston', 'bot',
|
||||||
|
'boutique', 'box', 'br', 'bradesco', 'bridgestone', 'broadway',
|
||||||
|
'broker', 'brother', 'brussels', 'bs', 'bt', 'budapest',
|
||||||
|
'bugatti', 'build', 'builders', 'business', 'buy', 'buzz', 'bv',
|
||||||
|
'bw', 'by', 'bz', 'bzh',
|
||||||
# c
|
# c
|
||||||
'ca', 'cab', 'cal', 'camera', 'camp', 'cancerresearch', 'canon',
|
'ca', 'cab', 'cafe', 'cal', 'call', 'calvinklein', 'cam',
|
||||||
'capetown', 'capital', 'caravan', 'cards', 'care', 'career',
|
'camera', 'camp', 'cancerresearch', 'canon', 'capetown',
|
||||||
'careers', 'cartier', 'casa', 'cash', 'casino', 'cat',
|
'capital', 'capitalone', 'car', 'caravan', 'cards', 'care',
|
||||||
'catering', 'cbn', 'cc', 'cd', 'center', 'ceo', 'cern', 'cf',
|
'career', 'careers', 'cars', 'cartier', 'casa', 'case', 'caseih',
|
||||||
'cg', 'ch', 'channel', 'chat', 'cheap', 'christmas', 'chrome',
|
'cash', 'casino', 'cat', 'catering', 'catholic', 'cba', 'cbn',
|
||||||
'church', 'ci', 'citic', 'city', 'ck', 'cl', 'claims',
|
'cbre', 'cbs', 'cc', 'cd', 'ceb', 'center', 'ceo', 'cern', 'cf',
|
||||||
'cleaning', 'click', 'clinic', 'clothing', 'club', 'cm', 'cn',
|
'cfa', 'cfd', 'cg', 'ch', 'chanel', 'channel', 'chase', 'chat',
|
||||||
'co', 'coach', 'codes', 'coffee', 'college', 'cologne', 'com',
|
'cheap', 'chintai', 'christmas', 'chrome', 'chrysler', 'church',
|
||||||
'community', 'company', 'computer', 'condos', 'construction',
|
'ci', 'cipriani', 'circle', 'cisco', 'citadel', 'citi', 'citic',
|
||||||
'consulting', 'contractors', 'cooking', 'cool', 'coop',
|
'city', 'cityeats', 'ck', 'cl', 'claims', 'cleaning', 'click',
|
||||||
'country', 'cr', 'credit', 'creditcard', 'cricket', 'crs',
|
'clinic', 'clinique', 'clothing', 'cloud', 'club', 'clubmed',
|
||||||
'cruises', 'cu', 'cuisinella', 'cv', 'cw', 'cx', 'cy', 'cymru',
|
'cm', 'cn', 'co', 'coach', 'codes', 'coffee', 'college',
|
||||||
'cz',
|
'cologne', 'com', 'comcast', 'commbank', 'community', 'company',
|
||||||
|
'compare', 'computer', 'comsec', 'condos', 'construction',
|
||||||
|
'consulting', 'contact', 'contractors', 'cooking',
|
||||||
|
'cookingchannel', 'cool', 'coop', 'corsica', 'country', 'coupon',
|
||||||
|
'coupons', 'courses', 'cr', 'credit', 'creditcard',
|
||||||
|
'creditunion', 'cricket', 'crown', 'crs', 'cruise', 'cruises',
|
||||||
|
'csc', 'cu', 'cuisinella', 'cv', 'cw', 'cx', 'cy', 'cymru',
|
||||||
|
'cyou', 'cz',
|
||||||
# d
|
# d
|
||||||
'dabur', 'dad', 'dance', 'dating', 'day', 'dclk', 'de', 'deals',
|
'dabur', 'dad', 'dance', 'data', 'date', 'dating', 'datsun',
|
||||||
'degree', 'delivery', 'democrat', 'dental', 'dentist', 'desi',
|
'day', 'dclk', 'dds', 'de', 'deal', 'dealer', 'deals', 'degree',
|
||||||
'design', 'dev', 'diamonds', 'diet', 'digital', 'direct',
|
'delivery', 'dell', 'deloitte', 'delta', 'democrat', 'dental',
|
||||||
'directory', 'discount', 'dj', 'dk', 'dm', 'dnp', 'do', 'docs',
|
'dentist', 'desi', 'design', 'dev', 'dhl', 'diamonds', 'diet',
|
||||||
'domains', 'doosan', 'durban', 'dvag', 'dz',
|
'digital', 'direct', 'directory', 'discount', 'discover', 'dish',
|
||||||
|
'diy', 'dj', 'dk', 'dm', 'dnp', 'do', 'docs', 'doctor', 'dodge',
|
||||||
|
'dog', 'doha', 'domains', 'dot', 'download', 'drive', 'dtv',
|
||||||
|
'dubai', 'duck', 'dunlop', 'duns', 'dupont', 'durban', 'dvag',
|
||||||
|
'dvr', 'dz',
|
||||||
# e
|
# e
|
||||||
'eat', 'ec', 'edu', 'education', 'ee', 'eg', 'email', 'emerck',
|
'earth', 'eat', 'ec', 'eco', 'edeka', 'edu', 'education', 'ee',
|
||||||
'energy', 'engineer', 'engineering', 'enterprises', 'equipment',
|
'eg', 'email', 'emerck', 'energy', 'engineer', 'engineering',
|
||||||
'er', 'es', 'esq', 'estate', 'et', 'eu', 'eurovision', 'eus',
|
'enterprises', 'epost', 'epson', 'equipment', 'er', 'ericsson',
|
||||||
'events', 'everbank', 'exchange', 'expert', 'exposed',
|
'erni', 'es', 'esq', 'estate', 'esurance', 'et', 'etisalat',
|
||||||
|
'eu', 'eurovision', 'eus', 'events', 'everbank', 'exchange',
|
||||||
|
'expert', 'exposed', 'express', 'extraspace',
|
||||||
# f
|
# f
|
||||||
'fail', 'fans', 'farm', 'fashion', 'feedback', 'fi', 'finance',
|
'fage', 'fail', 'fairwinds', 'faith', 'family', 'fan', 'fans',
|
||||||
'financial', 'firmdale', 'fish', 'fishing', 'fit', 'fitness',
|
'farm', 'farmers', 'fashion', 'fast', 'fedex', 'feedback',
|
||||||
'fj', 'fk', 'flights', 'florist', 'flowers', 'flsmidth', 'fly',
|
'ferrari', 'ferrero', 'fi', 'fiat', 'fidelity', 'fido', 'film',
|
||||||
'fm', 'fo', 'foo', 'football', 'forsale', 'foundation', 'fr',
|
'final', 'finance', 'financial', 'fire', 'firestone', 'firmdale',
|
||||||
'frl', 'frogans', 'fund', 'furniture', 'futbol',
|
'fish', 'fishing', 'fit', 'fitness', 'fj', 'fk', 'flickr',
|
||||||
|
'flights', 'flir', 'florist', 'flowers', 'fly', 'fm', 'fo',
|
||||||
|
'foo', 'food', 'foodnetwork', 'football', 'ford', 'forex',
|
||||||
|
'forsale', 'forum', 'foundation', 'fox', 'fr', 'free',
|
||||||
|
'fresenius', 'frl', 'frogans', 'frontdoor', 'frontier', 'ftr',
|
||||||
|
'fujitsu', 'fujixerox', 'fun', 'fund', 'furniture', 'futbol',
|
||||||
|
'fyi',
|
||||||
# g
|
# g
|
||||||
'ga', 'gal', 'gallery', 'garden', 'gb', 'gbiz', 'gd', 'gdn',
|
'ga', 'gal', 'gallery', 'gallo', 'gallup', 'game', 'games',
|
||||||
'ge', 'gent', 'gf', 'gg', 'ggee', 'gh', 'gi', 'gift', 'gifts',
|
'gap', 'garden', 'gb', 'gbiz', 'gd', 'gdn', 'ge', 'gea', 'gent',
|
||||||
'gives', 'gl', 'glass', 'gle', 'global', 'globo', 'gm', 'gmail',
|
'genting', 'george', 'gf', 'gg', 'ggee', 'gh', 'gi', 'gift',
|
||||||
'gmo', 'gmx', 'gn', 'goldpoint', 'goog', 'google', 'gop', 'gov',
|
'gifts', 'gives', 'giving', 'gl', 'glade', 'glass', 'gle',
|
||||||
'gp', 'gq', 'gr', 'graphics', 'gratis', 'green', 'gripe', 'gs',
|
'global', 'globo', 'gm', 'gmail', 'gmbh', 'gmo', 'gmx', 'gn',
|
||||||
'gt', 'gu', 'guide', 'guitars', 'guru', 'gw', 'gy',
|
'godaddy', 'gold', 'goldpoint', 'golf', 'goo', 'goodhands',
|
||||||
|
'goodyear', 'goog', 'google', 'gop', 'got', 'gov', 'gp', 'gq',
|
||||||
|
'gr', 'grainger', 'graphics', 'gratis', 'green', 'gripe',
|
||||||
|
'grocery', 'group', 'gs', 'gt', 'gu', 'guardian', 'gucci',
|
||||||
|
'guge', 'guide', 'guitars', 'guru', 'gw', 'gy',
|
||||||
# h
|
# h
|
||||||
'hamburg', 'hangout', 'haus', 'healthcare', 'help', 'here',
|
'hair', 'hamburg', 'hangout', 'haus', 'hbo', 'hdfc', 'hdfcbank',
|
||||||
'hermes', 'hiphop', 'hiv', 'hk', 'hm', 'hn', 'holdings',
|
'health', 'healthcare', 'help', 'helsinki', 'here', 'hermes',
|
||||||
'holiday', 'homes', 'horse', 'host', 'hosting', 'house', 'how',
|
'hgtv', 'hiphop', 'hisamitsu', 'hitachi', 'hiv', 'hk', 'hkt',
|
||||||
'hr', 'ht', 'hu',
|
'hm', 'hn', 'hockey', 'holdings', 'holiday', 'homedepot',
|
||||||
|
'homegoods', 'homes', 'homesense', 'honda', 'honeywell', 'horse',
|
||||||
|
'hospital', 'host', 'hosting', 'hot', 'hoteles', 'hotels',
|
||||||
|
'hotmail', 'house', 'how', 'hr', 'hsbc', 'ht', 'hu', 'hughes',
|
||||||
|
'hyatt', 'hyundai',
|
||||||
# i
|
# i
|
||||||
'ibm', 'id', 'ie', 'ifm', 'il', 'im', 'immo', 'immobilien', 'in',
|
'ibm', 'icbc', 'ice', 'icu', 'id', 'ie', 'ieee', 'ifm', 'ikano',
|
||||||
'industries', 'info', 'ing', 'ink', 'institute', 'insure', 'int',
|
'il', 'im', 'imamat', 'imdb', 'immo', 'immobilien', 'in',
|
||||||
'international', 'investments', 'io', 'iq', 'ir', 'irish', 'is',
|
'industries', 'infiniti', 'info', 'ing', 'ink', 'institute',
|
||||||
'it', 'iwc',
|
'insurance', 'insure', 'int', 'intel', 'international', 'intuit',
|
||||||
|
'investments', 'io', 'ipiranga', 'iq', 'ir', 'irish', 'is',
|
||||||
|
'iselect', 'ismaili', 'ist', 'istanbul', 'it', 'itau', 'itv',
|
||||||
|
'iveco', 'iwc',
|
||||||
# j
|
# j
|
||||||
'jcb', 'je', 'jetzt', 'jm', 'jo', 'jobs', 'joburg', 'jp',
|
'jaguar', 'java', 'jcb', 'jcp', 'je', 'jeep', 'jetzt', 'jewelry',
|
||||||
'juegos',
|
'jio', 'jlc', 'jll', 'jm', 'jmp', 'jnj', 'jo', 'jobs', 'joburg',
|
||||||
|
'jot', 'joy', 'jp', 'jpmorgan', 'jprs', 'juegos', 'juniper',
|
||||||
# k
|
# k
|
||||||
'kaufen', 'kddi', 'ke', 'kg', 'kh', 'ki', 'kim', 'kitchen',
|
'kaufen', 'kddi', 'ke', 'kerryhotels', 'kerrylogistics',
|
||||||
'kiwi', 'km', 'kn', 'koeln', 'kp', 'kr', 'krd', 'kred', 'kw',
|
'kerryproperties', 'kfh', 'kg', 'kh', 'ki', 'kia', 'kim',
|
||||||
'ky', 'kyoto', 'kz',
|
'kinder', 'kindle', 'kitchen', 'kiwi', 'km', 'kn', 'koeln',
|
||||||
|
'komatsu', 'kosher', 'kp', 'kpmg', 'kpn', 'kr', 'krd', 'kred',
|
||||||
|
'kuokgroup', 'kw', 'ky', 'kyoto', 'kz',
|
||||||
# l
|
# l
|
||||||
'la', 'lacaixa', 'land', 'lat', 'latrobe', 'lawyer', 'lb', 'lc',
|
'la', 'lacaixa', 'ladbrokes', 'lamborghini', 'lamer',
|
||||||
'lds', 'lease', 'legal', 'lgbt', 'li', 'lidl', 'life',
|
'lancaster', 'lancia', 'lancome', 'land', 'landrover', 'lanxess',
|
||||||
'lighting', 'limited', 'limo', 'link', 'lk', 'loans',
|
'lasalle', 'lat', 'latino', 'latrobe', 'law', 'lawyer', 'lb',
|
||||||
'localhost', 'london', 'lotte', 'lotto', 'lr', 'ls', 'lt',
|
'lc', 'lds', 'lease', 'leclerc', 'lefrak', 'legal', 'lego',
|
||||||
'ltda', 'lu', 'luxe', 'luxury', 'lv', 'ly',
|
'lexus', 'lgbt', 'li', 'liaison', 'lidl', 'life',
|
||||||
|
'lifeinsurance', 'lifestyle', 'lighting', 'like', 'lilly',
|
||||||
|
'limited', 'limo', 'lincoln', 'linde', 'link', 'lipsy', 'live',
|
||||||
|
'living', 'lixil', 'lk', 'loan', 'loans', 'localhost', 'locker',
|
||||||
|
'locus', 'loft', 'lol', 'london', 'lotte', 'lotto', 'love',
|
||||||
|
'lpl', 'lplfinancial', 'lr', 'ls', 'lt', 'ltd', 'ltda', 'lu',
|
||||||
|
'lundbeck', 'lupin', 'luxe', 'luxury', 'lv', 'ly',
|
||||||
# m
|
# m
|
||||||
'ma', 'madrid', 'maison', 'management', 'mango', 'market',
|
'ma', 'macys', 'madrid', 'maif', 'maison', 'makeup', 'man',
|
||||||
'marketing', 'marriott', 'mc', 'md', 'me', 'media', 'meet',
|
'management', 'mango', 'map', 'market', 'marketing', 'markets',
|
||||||
'melbourne', 'meme', 'memorial', 'menu', 'mg', 'mh', 'miami',
|
'marriott', 'marshalls', 'maserati', 'mattel', 'mba', 'mc',
|
||||||
'mil', 'mini', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'moda',
|
'mckinsey', 'md', 'me', 'med', 'media', 'meet', 'melbourne',
|
||||||
'moe', 'monash', 'money', 'mormon', 'mortgage', 'moscow',
|
'meme', 'memorial', 'men', 'menu', 'meo', 'merckmsd', 'metlife',
|
||||||
'motorcycles', 'mov', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu',
|
'mg', 'mh', 'miami', 'microsoft', 'mil', 'mini', 'mint', 'mit',
|
||||||
'museum', 'mv', 'mw', 'mx', 'my', 'mz',
|
'mitsubishi', 'mk', 'ml', 'mlb', 'mls', 'mm', 'mma', 'mn', 'mo',
|
||||||
|
'mobi', 'mobile', 'mobily', 'moda', 'moe', 'moi', 'mom',
|
||||||
|
'monash', 'money', 'monster', 'mopar', 'mormon', 'mortgage',
|
||||||
|
'moscow', 'moto', 'motorcycles', 'mov', 'movie', 'movistar',
|
||||||
|
'mp', 'mq', 'mr', 'ms', 'msd', 'mt', 'mtn', 'mtr', 'mu',
|
||||||
|
'museum', 'mutual', 'mv', 'mw', 'mx', 'my', 'mz',
|
||||||
# n
|
# n
|
||||||
'na', 'nagoya', 'name', 'navy', 'nc', 'ne', 'net', 'network',
|
'na', 'nab', 'nadex', 'nagoya', 'name', 'nationwide', 'natura',
|
||||||
'neustar', 'new', 'nexus', 'nf', 'ng', 'ngo', 'nhk', 'ni',
|
'navy', 'nba', 'nc', 'ne', 'nec', 'net', 'netbank', 'netflix',
|
||||||
'nico', 'ninja', 'nl', 'no', 'np', 'nr', 'nra', 'nrw', 'ntt',
|
'network', 'neustar', 'new', 'newholland', 'news', 'next',
|
||||||
'nu', 'nyc', 'nz',
|
'nextdirect', 'nexus', 'nf', 'nfl', 'ng', 'ngo', 'nhk', 'ni',
|
||||||
|
'nico', 'nike', 'nikon', 'ninja', 'nissan', 'nissay', 'nl', 'no',
|
||||||
|
'nokia', 'northwesternmutual', 'norton', 'now', 'nowruz',
|
||||||
|
'nowtv', 'np', 'nr', 'nra', 'nrw', 'ntt', 'nu', 'nyc', 'nz',
|
||||||
# o
|
# o
|
||||||
'okinawa', 'om', 'one', 'ong', 'onl', 'ooo', 'org', 'organic',
|
'obi', 'observer', 'off', 'office', 'okinawa', 'olayan',
|
||||||
'osaka', 'otsuka', 'ovh',
|
'olayangroup', 'oldnavy', 'ollo', 'om', 'omega', 'one', 'ong',
|
||||||
|
'onl', 'online', 'onyourside', 'ooo', 'open', 'oracle', 'orange',
|
||||||
|
'org', 'organic', 'origins', 'osaka', 'otsuka', 'ott', 'ovh',
|
||||||
# p
|
# p
|
||||||
'pa', 'paris', 'partners', 'parts', 'party', 'pe', 'pf', 'pg',
|
'pa', 'page', 'panasonic', 'panerai', 'paris', 'pars',
|
||||||
'ph', 'pharmacy', 'photo', 'photography', 'photos', 'physio',
|
'partners', 'parts', 'party', 'passagens', 'pay', 'pccw', 'pe',
|
||||||
'pics', 'pictures', 'pink', 'pizza', 'pk', 'pl', 'place',
|
'pet', 'pf', 'pfizer', 'pg', 'ph', 'pharmacy', 'phd', 'philips',
|
||||||
'plumbing', 'pm', 'pn', 'pohl', 'poker', 'porn', 'post', 'pr',
|
'phone', 'photo', 'photography', 'photos', 'physio', 'piaget',
|
||||||
'praxi', 'press', 'pro', 'prod', 'productions', 'prof',
|
'pics', 'pictet', 'pictures', 'pid', 'pin', 'ping', 'pink',
|
||||||
'properties', 'property', 'ps', 'pt', 'pub', 'pw', 'py',
|
'pioneer', 'pizza', 'pk', 'pl', 'place', 'play', 'playstation',
|
||||||
|
'plumbing', 'plus', 'pm', 'pn', 'pnc', 'pohl', 'poker',
|
||||||
|
'politie', 'porn', 'post', 'pr', 'pramerica', 'praxi', 'press',
|
||||||
|
'prime', 'pro', 'prod', 'productions', 'prof', 'progressive',
|
||||||
|
'promo', 'properties', 'property', 'protection', 'pru',
|
||||||
|
'prudential', 'ps', 'pt', 'pub', 'pw', 'pwc', 'py',
|
||||||
# q
|
# q
|
||||||
'qa', 'qpon', 'quebec',
|
'qa', 'qpon', 'quebec', 'quest', 'qvc',
|
||||||
# r
|
# r
|
||||||
're', 'realtor', 'recipes', 'red', 'rehab', 'reise', 'reisen',
|
'racing', 'radio', 'raid', 're', 'read', 'realestate', 'realtor',
|
||||||
'reit', 'ren', 'rentals', 'repair', 'report', 'republican',
|
'realty', 'recipes', 'red', 'redstone', 'redumbrella', 'rehab',
|
||||||
'rest', 'restaurant', 'reviews', 'rich', 'rio', 'rip', 'ro',
|
'reise', 'reisen', 'reit', 'reliance', 'ren', 'rent', 'rentals',
|
||||||
'rocks', 'rodeo', 'rs', 'rsvp', 'ru', 'ruhr', 'rw', 'ryukyu',
|
'repair', 'report', 'republican', 'rest', 'restaurant', 'review',
|
||||||
|
'reviews', 'rexroth', 'rich', 'richardli', 'ricoh',
|
||||||
|
'rightathome', 'ril', 'rio', 'rip', 'rmit', 'ro', 'rocher',
|
||||||
|
'rocks', 'rodeo', 'rogers', 'room', 'rs', 'rsvp', 'ru', 'rugby',
|
||||||
|
'ruhr', 'run', 'rw', 'rwe', 'ryukyu',
|
||||||
# s
|
# s
|
||||||
'sa', 'saarland', 'sale', 'samsung', 'sarl', 'saxo', 'sb', 'sc',
|
'sa', 'saarland', 'safe', 'safety', 'sakura', 'sale', 'salon',
|
||||||
'sca', 'scb', 'schmidt', 'school', 'schule', 'schwarz',
|
'samsclub', 'samsung', 'sandvik', 'sandvikcoromant', 'sanofi',
|
||||||
'science', 'scot', 'sd', 'se', 'services', 'sew', 'sexy', 'sg',
|
'sap', 'sapo', 'sarl', 'sas', 'save', 'saxo', 'sb', 'sbi', 'sbs',
|
||||||
'sh', 'shiksha', 'shoes', 'shriram', 'si', 'singles', 'sj', 'sk',
|
'sc', 'sca', 'scb', 'schaeffler', 'schmidt', 'scholarships',
|
||||||
'sky', 'sl', 'sm', 'sn', 'so', 'social', 'software', 'sohu',
|
'school', 'schule', 'schwarz', 'science', 'scjohnson', 'scor',
|
||||||
'solar', 'solutions', 'soy', 'space', 'spiegel', 'sr', 'st',
|
'scot', 'sd', 'se', 'search', 'seat', 'secure', 'security',
|
||||||
'style', 'su', 'supplies', 'supply', 'support', 'surf',
|
'seek', 'select', 'sener', 'services', 'ses', 'seven', 'sew',
|
||||||
'surgery', 'suzuki', 'sv', 'sx', 'sy', 'sydney', 'systems', 'sz',
|
'sex', 'sexy', 'sfr', 'sg', 'sh', 'shangrila', 'sharp', 'shaw',
|
||||||
|
'shell', 'shia', 'shiksha', 'shoes', 'shop', 'shopping',
|
||||||
|
'shouji', 'show', 'showtime', 'shriram', 'si', 'silk', 'sina',
|
||||||
|
'singles', 'site', 'sj', 'sk', 'ski', 'skin', 'sky', 'skype',
|
||||||
|
'sl', 'sling', 'sm', 'smart', 'smile', 'sn', 'sncf', 'so',
|
||||||
|
'soccer', 'social', 'softbank', 'software', 'sohu', 'solar',
|
||||||
|
'solutions', 'song', 'sony', 'soy', 'space', 'spiegel', 'spot',
|
||||||
|
'spreadbetting', 'sr', 'srl', 'srt', 'st', 'stada', 'staples',
|
||||||
|
'star', 'starhub', 'statebank', 'statefarm', 'statoil', 'stc',
|
||||||
|
'stcgroup', 'stockholm', 'storage', 'store', 'stream', 'studio',
|
||||||
|
'study', 'style', 'su', 'sucks', 'supplies', 'supply', 'support',
|
||||||
|
'surf', 'surgery', 'suzuki', 'sv', 'swatch', 'swiftcover',
|
||||||
|
'swiss', 'sx', 'sy', 'sydney', 'symantec', 'systems', 'sz',
|
||||||
# t
|
# t
|
||||||
'taipei', 'tatar', 'tattoo', 'tax', 'tc', 'td', 'technology',
|
'tab', 'taipei', 'talk', 'taobao', 'target', 'tatamotors',
|
||||||
'tel', 'temasek', 'tennis', 'tf', 'tg', 'th', 'tienda', 'tips',
|
'tatar', 'tattoo', 'tax', 'taxi', 'tc', 'tci', 'td', 'tdk',
|
||||||
'tires', 'tirol', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'today',
|
'team', 'tech', 'technology', 'tel', 'telecity', 'telefonica',
|
||||||
'tokyo', 'tools', 'top', 'toshiba', 'town', 'toys', 'tp', 'tr',
|
'temasek', 'tennis', 'teva', 'tf', 'tg', 'th', 'thd', 'theater',
|
||||||
'trade', 'training', 'travel', 'trust', 'tt', 'tui', 'tv', 'tw',
|
'theatre', 'tiaa', 'tickets', 'tienda', 'tiffany', 'tips',
|
||||||
'tz',
|
'tires', 'tirol', 'tj', 'tjmaxx', 'tjx', 'tk', 'tkmaxx', 'tl',
|
||||||
|
'tm', 'tmall', 'tn', 'to', 'today', 'tokyo', 'tools', 'top',
|
||||||
|
'toray', 'toshiba', 'total', 'tours', 'town', 'toyota', 'toys',
|
||||||
|
'tr', 'trade', 'trading', 'training', 'travel', 'travelchannel',
|
||||||
|
'travelers', 'travelersinsurance', 'trust', 'trv', 'tt', 'tube',
|
||||||
|
'tui', 'tunes', 'tushu', 'tv', 'tvs', 'tw', 'tz',
|
||||||
# u
|
# u
|
||||||
'ua', 'ug', 'uk', 'university', 'uno', 'uol', 'us', 'uy', 'uz',
|
'ua', 'ubank', 'ubs', 'uconnect', 'ug', 'uk', 'unicom',
|
||||||
|
'university', 'uno', 'uol', 'ups', 'us', 'uy', 'uz',
|
||||||
# v
|
# v
|
||||||
'va', 'vacations', 'vc', 've', 'vegas', 'ventures',
|
'va', 'vacations', 'vana', 'vanguard', 'vc', 've', 'vegas',
|
||||||
'versicherung', 'vet', 'vg', 'vi', 'viajes', 'video', 'villas',
|
'ventures', 'verisign', 'versicherung', 'vet', 'vg', 'vi',
|
||||||
'vision', 'vlaanderen', 'vn', 'vodka', 'vote', 'voting', 'voto',
|
'viajes', 'video', 'vig', 'viking', 'villas', 'vin', 'vip',
|
||||||
'voyage', 'vu',
|
'virgin', 'visa', 'vision', 'vista', 'vistaprint', 'viva',
|
||||||
|
'vivo', 'vlaanderen', 'vn', 'vodka', 'volkswagen', 'volvo',
|
||||||
|
'vote', 'voting', 'voto', 'voyage', 'vu', 'vuelos',
|
||||||
# w
|
# w
|
||||||
'wales', 'wang', 'watch', 'webcam', 'website', 'wed', 'wedding',
|
'wales', 'walmart', 'walter', 'wang', 'wanggou', 'warman',
|
||||||
'wf', 'whoswho', 'wien', 'wiki', 'williamhill', 'wme', 'work',
|
'watch', 'watches', 'weather', 'weatherchannel', 'webcam',
|
||||||
'works', 'world', 'ws', 'wtc', 'wtf',
|
'weber', 'website', 'wed', 'wedding', 'weibo', 'weir', 'wf',
|
||||||
|
'whoswho', 'wien', 'wiki', 'williamhill', 'win', 'windows',
|
||||||
|
'wine', 'winners', 'wme', 'wolterskluwer', 'woodside', 'work',
|
||||||
|
'works', 'world', 'wow', 'ws', 'wtc', 'wtf',
|
||||||
# x
|
# x
|
||||||
'xn--1qqw23a', 'xn--3bst00m', 'xn--3ds443g', 'xn--3e0b707e',
|
'xbox', 'xerox', 'xfinity', 'xihuan', 'xin', 'xn--11b4c3d',
|
||||||
'xn--45brj9c', 'xn--45q11c', 'xn--4gbrim', 'xn--55qw42g',
|
'xn--1ck2e1b', 'xn--1qqw23a', 'xn--2scrj9c', 'xn--30rr7y',
|
||||||
'xn--55qx5d', 'xn--6frz82g', 'xn--6qq986b3xl', 'xn--80adxhks',
|
'xn--3bst00m', 'xn--3ds443g', 'xn--3e0b707e', 'xn--3hcrj9c',
|
||||||
'xn--80ao21a', 'xn--80asehdb', 'xn--80aswg', 'xn--90a3ac',
|
'xn--3oq18vl8pn36a', 'xn--3pxu8k', 'xn--42c2d9a', 'xn--45br5cyl',
|
||||||
'xn--90ais', 'xn--b4w605ferd', 'xn--c1avg', 'xn--cg4bki',
|
'xn--45brj9c', 'xn--45q11c', 'xn--4gbrim', 'xn--54b7fta0cc',
|
||||||
'xn--clchc0ea0b2g2a9gcd', 'xn--czr694b', 'xn--czrs0t',
|
'xn--55qw42g', 'xn--55qx5d', 'xn--5su34j936bgsg', 'xn--5tzm5g',
|
||||||
'xn--czru2d', 'xn--d1acj3b', 'xn--d1alf', 'xn--fiq228c5hs',
|
'xn--6frz82g', 'xn--6qq986b3xl', 'xn--80adxhks', 'xn--80ao21a',
|
||||||
'xn--fiq64b', 'xn--fiqs8s', 'xn--fiqz9s', 'xn--flw351e',
|
'xn--80aqecdr1a', 'xn--80asehdb', 'xn--80aswg', 'xn--8y0a063a',
|
||||||
'xn--fpcrj9c3d', 'xn--fzc2c9e2c', 'xn--gecrj9c', 'xn--h2brj9c',
|
'xn--90a3ac', 'xn--90ae', 'xn--90ais', 'xn--9dbq2a',
|
||||||
'xn--hxt814e', 'xn--i1b6b1a6a2e', 'xn--io0a7i', 'xn--j1amh',
|
'xn--9et52u', 'xn--9krt00a', 'xn--b4w605ferd',
|
||||||
'xn--j6w193g', 'xn--kprw13d', 'xn--kpry57d', 'xn--kput3i',
|
'xn--bck1b9a5dre4c', 'xn--c1avg', 'xn--c2br7g', 'xn--cck2b3b',
|
||||||
'xn--l1acc', 'xn--lgbbat1ad8j', 'xn--mgb9awbf',
|
'xn--cg4bki', 'xn--clchc0ea0b2g2a9gcd', 'xn--czr694b',
|
||||||
'xn--mgba3a4f16a', 'xn--mgbaam7a8h', 'xn--mgbab2bd',
|
'xn--czrs0t', 'xn--czru2d', 'xn--d1acj3b', 'xn--d1alf',
|
||||||
'xn--mgbayh7gpa', 'xn--mgbbh1a71e', 'xn--mgbc0a9azcg',
|
'xn--e1a4c', 'xn--eckvdtc9d', 'xn--efvy88h', 'xn--estv75g',
|
||||||
'xn--mgberp4a5d4ar', 'xn--mgbx4cd0ab', 'xn--ngbc5azd',
|
'xn--fct429k', 'xn--fhbei', 'xn--fiq228c5hs', 'xn--fiq64b',
|
||||||
'xn--node', 'xn--nqv7f', 'xn--nqv7fs00ema', 'xn--o3cw4h',
|
'xn--fiqs8s', 'xn--fiqz9s', 'xn--fjq720a', 'xn--flw351e',
|
||||||
'xn--ogbpf8fl', 'xn--p1acf', 'xn--p1ai', 'xn--pgbs0dh',
|
'xn--fpcrj9c3d', 'xn--fzc2c9e2c', 'xn--fzys8d69uvgm',
|
||||||
'xn--q9jyb4c', 'xn--qcka1pmc', 'xn--rhqv96g', 'xn--s9brj9c',
|
'xn--g2xx48c', 'xn--gckr3f0f', 'xn--gecrj9c', 'xn--gk3at1e',
|
||||||
'xn--ses554g', 'xn--unup4y', 'xn--vermgensberater-ctb',
|
'xn--h2breg3eve', 'xn--h2brj9c', 'xn--h2brj9c8c', 'xn--hxt814e',
|
||||||
'xn--vermgensberatung-pwb', 'xn--vhquv', 'xn--wgbh1c',
|
'xn--i1b6b1a6a2e', 'xn--imr513n', 'xn--io0a7i', 'xn--j1aef',
|
||||||
|
'xn--j1amh', 'xn--j6w193g', 'xn--jlq61u9w7b', 'xn--jvr189m',
|
||||||
|
'xn--kcrx77d1x4a', 'xn--kprw13d', 'xn--kpry57d', 'xn--kpu716f',
|
||||||
|
'xn--kput3i', 'xn--l1acc', 'xn--lgbbat1ad8j', 'xn--mgb9awbf',
|
||||||
|
'xn--mgba3a3ejt', 'xn--mgba3a4f16a', 'xn--mgba7c0bbn0a',
|
||||||
|
'xn--mgbaakc7dvf', 'xn--mgbaam7a8h', 'xn--mgbab2bd',
|
||||||
|
'xn--mgbai9azgqp6j', 'xn--mgbayh7gpa', 'xn--mgbb9fbpob',
|
||||||
|
'xn--mgbbh1a', 'xn--mgbbh1a71e', 'xn--mgbc0a9azcg',
|
||||||
|
'xn--mgbca7dzdo', 'xn--mgberp4a5d4ar', 'xn--mgbgu82a',
|
||||||
|
'xn--mgbi4ecexp', 'xn--mgbpl2fh', 'xn--mgbt3dhd', 'xn--mgbtx2b',
|
||||||
|
'xn--mgbx4cd0ab', 'xn--mix891f', 'xn--mk1bu44c', 'xn--mxtq1m',
|
||||||
|
'xn--ngbc5azd', 'xn--ngbe9e0a', 'xn--ngbrx', 'xn--node',
|
||||||
|
'xn--nqv7f', 'xn--nqv7fs00ema', 'xn--nyqy26a', 'xn--o3cw4h',
|
||||||
|
'xn--ogbpf8fl', 'xn--p1acf', 'xn--p1ai', 'xn--pbt977c',
|
||||||
|
'xn--pgbs0dh', 'xn--pssy2u', 'xn--q9jyb4c', 'xn--qcka1pmc',
|
||||||
|
'xn--qxam', 'xn--rhqv96g', 'xn--rovu88b', 'xn--rvc1e0am3e',
|
||||||
|
'xn--s9brj9c', 'xn--ses554g', 'xn--t60b56a', 'xn--tckwe',
|
||||||
|
'xn--tiq49xqyj', 'xn--unup4y', 'xn--vermgensberater-ctb',
|
||||||
|
'xn--vermgensberatung-pwb', 'xn--vhquv', 'xn--vuq861b',
|
||||||
|
'xn--w4r85el8fhu5dnra', 'xn--w4rs40l', 'xn--wgbh1c',
|
||||||
'xn--wgbl6a', 'xn--xhq521b', 'xn--xkc2al3hye2a',
|
'xn--wgbl6a', 'xn--xhq521b', 'xn--xkc2al3hye2a',
|
||||||
'xn--xkc2dl3a5ee0h', 'xn--yfro4i67o', 'xn--ygbi2ammx',
|
'xn--xkc2dl3a5ee0h', 'xn--y9a3aq', 'xn--yfro4i67o',
|
||||||
'xn--zfr164b', 'xxx', 'xyz',
|
'xn--ygbi2ammx', 'xn--zfr164b', 'xperia', 'xxx', 'xyz',
|
||||||
# y
|
# y
|
||||||
'yachts', 'yandex', 'ye', 'yodobashi', 'yoga', 'yokohama',
|
'yachts', 'yahoo', 'yamaxun', 'yandex', 'ye', 'yodobashi',
|
||||||
'youtube', 'yt',
|
'yoga', 'yokohama', 'you', 'youtube', 'yt', 'yun',
|
||||||
# z
|
# z
|
||||||
'za', 'zip', 'zm', 'zone', 'zuerich', 'zw'
|
'za', 'zappos', 'zara', 'zero', 'zip', 'zippo', 'zm', 'zone',
|
||||||
|
'zuerich', 'zw'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -2622,7 +2746,7 @@ class ANY_OF(Validator):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
for validator in self.subs:
|
for validator in self.subs:
|
||||||
value, error = validator(value)
|
value, error = validator(value)
|
||||||
if error == None:
|
if error is None:
|
||||||
break
|
break
|
||||||
return value, error
|
return value, error
|
||||||
|
|
||||||
@@ -2762,7 +2886,7 @@ class LazyCrypt(object):
|
|||||||
else:
|
else:
|
||||||
digest_alg, key = self.crypt.digest_alg, ''
|
digest_alg, key = self.crypt.digest_alg, ''
|
||||||
if self.crypt.salt:
|
if self.crypt.salt:
|
||||||
if self.crypt.salt == True:
|
if self.crypt.salt is True:
|
||||||
salt = str(web2py_uuid()).replace('-', '')[-16:]
|
salt = str(web2py_uuid()).replace('-', '')[-16:]
|
||||||
else:
|
else:
|
||||||
salt = self.crypt.salt
|
salt = self.crypt.salt
|
||||||
@@ -2847,7 +2971,7 @@ class CRYPT(object):
|
|||||||
Supports standard algorithms
|
Supports standard algorithms
|
||||||
|
|
||||||
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
|
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
|
||||||
... print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
|
... print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
|
||||||
md5$...$...
|
md5$...$...
|
||||||
sha1$...$...
|
sha1$...$...
|
||||||
sha256$...$...
|
sha256$...$...
|
||||||
@@ -2859,13 +2983,13 @@ class CRYPT(object):
|
|||||||
Supports for pbkdf2
|
Supports for pbkdf2
|
||||||
|
|
||||||
>>> alg = 'pbkdf2(1000,20,sha512)'
|
>>> alg = 'pbkdf2(1000,20,sha512)'
|
||||||
>>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
|
>>> print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
|
||||||
pbkdf2(1000,20,sha512)$...$...
|
pbkdf2(1000,20,sha512)$...$...
|
||||||
|
|
||||||
An optional hmac_key can be specified and it is used as salt prefix
|
An optional hmac_key can be specified and it is used as salt prefix
|
||||||
|
|
||||||
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
|
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
|
||||||
>>> print a
|
>>> print(a)
|
||||||
md5$...$...
|
md5$...$...
|
||||||
|
|
||||||
Even if the algorithm changes the hash can still be validated
|
Even if the algorithm changes the hash can still be validated
|
||||||
|
|||||||
+6
-3
@@ -140,7 +140,7 @@ class web2pyDialog(object):
|
|||||||
else:
|
else:
|
||||||
import tkinter
|
import tkinter
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
|
||||||
bg_color = 'white'
|
bg_color = 'white'
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
@@ -463,7 +463,7 @@ class web2pyDialog(object):
|
|||||||
import tkMessageBox as messagebox
|
import tkMessageBox as messagebox
|
||||||
else:
|
else:
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
|
|
||||||
messagebox.showerror('web2py start server', message)
|
messagebox.showerror('web2py start server', message)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -1076,7 +1076,10 @@ def start_schedulers(options):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Work around OS X problem: http://bugs.python.org/issue9405
|
# Work around OS X problem: http://bugs.python.org/issue9405
|
||||||
import urllib
|
if PY2:
|
||||||
|
import urllib
|
||||||
|
else:
|
||||||
|
import urllib.request as urllib
|
||||||
urllib.getproxies()
|
urllib.getproxies()
|
||||||
|
|
||||||
for app in apps:
|
for app in apps:
|
||||||
|
|||||||
+9
-3
@@ -35,11 +35,17 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access'
|
URL_CHECK_ACCESS = 'http://127.0.0.1:8002/%(app)s/default/check_access'
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
def allow_access(environ,host):
|
def allow_access(environ,host):
|
||||||
|
if PY2:
|
||||||
|
import urllib2
|
||||||
|
from urllib import urlencode
|
||||||
|
else:
|
||||||
|
from urllib import request as urllib2
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import urllib
|
|
||||||
import urllib2
|
|
||||||
import datetime
|
import datetime
|
||||||
header = '%s @ %s ' % (datetime.datetime.now(),host) + '='*20
|
header = '%s @ %s ' % (datetime.datetime.now(),host) + '='*20
|
||||||
pprint = '\n'.join('%s:%s' % item for item in environ.items())
|
pprint = '\n'.join('%s:%s' % item for item in environ.items())
|
||||||
@@ -56,7 +62,7 @@ def allow_access(environ,host):
|
|||||||
if key.startswith('HTTP_'):
|
if key.startswith('HTTP_'):
|
||||||
headers[key[5:]] = environ[key] # this passes the cookies through!
|
headers[key[5:]] = environ[key] # this passes the cookies through!
|
||||||
try:
|
try:
|
||||||
data = urllib.urlencode({'request_uri':environ['REQUEST_URI']})
|
data = urlencode({'request_uri':environ['REQUEST_URI']})
|
||||||
request = urllib2.Request(URL_CHECK_ACCESS % dict(app=app),data,headers)
|
request = urllib2.Request(URL_CHECK_ACCESS % dict(app=app),data,headers)
|
||||||
response = urllib2.urlopen(request).read().strip().lower()
|
response = urllib2.urlopen(request).read().strip().lower()
|
||||||
if response.startswith('true'): return True
|
if response.startswith('true'): return True
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Written by Vinyl Darkscratch, www.queengoob.org
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# TODO: add comments
|
||||||
|
|
||||||
|
# This script can be run with no arguments (which sets the language folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (language folder path and default language).
|
||||||
|
# When run, this script will compare the original language's strings to their assigned values and determine if they match, just like web2py's interface. While some phrases may be the exact same between both languages, this should provide a good idea of how far along translations are.
|
||||||
|
|
||||||
|
def check_lang_progress(cwd, default_lang):
|
||||||
|
for x in os.listdir(cwd):
|
||||||
|
if x == default_lang or x.startswith("plural-"): continue
|
||||||
|
|
||||||
|
data = eval(open(os.path.join(cwd, x)).read())
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
translated = 0
|
||||||
|
|
||||||
|
for key in data:
|
||||||
|
total += 1
|
||||||
|
if key.replace('@markmin\x01', '') != data[key]: translated += 1
|
||||||
|
|
||||||
|
print "Translations for %s (%s): %d/%d Translated (%d Untranslated)" %(data['!langname!'], data['!langcode!'], translated, total, total-translated)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cwd = os.getcwd()
|
||||||
|
default_lang = 'en'
|
||||||
|
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
cwd = sys.argv[1]
|
||||||
|
default_lang = sys.argv[2]
|
||||||
|
elif len(sys.argv) > 1:
|
||||||
|
default_lang = sys.argv[1]
|
||||||
|
|
||||||
|
check_lang_progress(cwd, default_lang)
|
||||||
@@ -222,7 +222,7 @@ echo <<EOF
|
|||||||
you can stop uwsgi and nginx with
|
you can stop uwsgi and nginx with
|
||||||
|
|
||||||
sudo /etc/init.d/nginx stop
|
sudo /etc/init.d/nginx stop
|
||||||
sudo systemctl start emperor.uwsgi.service
|
sudo systemctl stop emperor.uwsgi.service
|
||||||
|
|
||||||
and start it with
|
and start it with
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ hashes = {}
|
|||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
if request.tickets_db:
|
if request.tickets_db:
|
||||||
print "You're storing tickets yet in database"
|
print("You're storing tickets yet in database")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for file in os.listdir(errors_path):
|
for file in os.listdir(errors_path):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import json
|
|||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print "missing module 'Requests', aborting."
|
print("missing module 'Requests', aborting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from gluon import URL
|
from gluon import URL
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Written by Vinyl Darkscratch, www.queengoob.org
|
||||||
|
|
||||||
|
# TODO: add comments
|
||||||
|
|
||||||
|
import os
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||||
|
parentdir = os.path.dirname(currentdir)
|
||||||
|
sys.path.insert(0, parentdir)
|
||||||
|
|
||||||
|
from gluon.cfs import getcfs
|
||||||
|
from gluon.utf8 import Utf8
|
||||||
|
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
|
||||||
|
from gluon.languages import findT
|
||||||
|
|
||||||
|
# This script can be run with no arguments (which sets the application folder to the current working directory, and default language to English), one argument (which sets the default language), or two arguments (application folder path and default language).
|
||||||
|
# When run, it will update the default language, as well as strip all of the strings found in the non-default languages but not in the default language, and add the strings found in the default language to the non-default languages it is not, making sure translators don't do additional work that will never be used.
|
||||||
|
|
||||||
|
def read_dict_aux(filename):
|
||||||
|
lang_text = open(filename, 'r').read().replace(b'\r\n', b'\n')
|
||||||
|
try:
|
||||||
|
return safe_eval(to_native(lang_text)) or {}
|
||||||
|
except Exception:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
status = 'Syntax error in %s (%s)' % (filename, e)
|
||||||
|
return {'__corrupted__': status}
|
||||||
|
|
||||||
|
def read_dict(filename):
|
||||||
|
return getcfs('lang:' + filename, filename, lambda: read_dict_aux(filename))
|
||||||
|
|
||||||
|
def safe_eval(text):
|
||||||
|
if text.strip():
|
||||||
|
try:
|
||||||
|
return ast.literal_eval(text)
|
||||||
|
except ImportError:
|
||||||
|
return eval(text, {}, {})
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sort_function(x, y):
|
||||||
|
return cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())
|
||||||
|
|
||||||
|
def write_file(file, contents):
|
||||||
|
file.write('# -*- coding: utf-8 -*-\n{\n')
|
||||||
|
for key in sorted(contents, sort_function):
|
||||||
|
file.write('%s: %s,\n' % (repr(Utf8(key)),
|
||||||
|
repr(Utf8(contents[key]))))
|
||||||
|
file.write('}\n')
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
def update_languages(cwd, default_lang):
|
||||||
|
defaultfp = os.path.join(cwd, "languages", '%s.py' %default_lang)
|
||||||
|
findT(cwd, default_lang)
|
||||||
|
default = read_dict(defaultfp)
|
||||||
|
|
||||||
|
for lang in os.listdir(os.path.join(cwd, "languages")):
|
||||||
|
if lang == default_lang+".py" or lang.startswith("plural-"): continue
|
||||||
|
|
||||||
|
i18n = read_dict(os.path.join(cwd, "languages", lang))
|
||||||
|
if i18n:
|
||||||
|
new_dict = default
|
||||||
|
for phrase in i18n:
|
||||||
|
if phrase in default:
|
||||||
|
new_dict[phrase] = i18n[phrase]
|
||||||
|
write_file(open(os.path.join(cwd, "languages", lang), 'w'), new_dict)
|
||||||
|
print lang
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cwd = os.getcwd()
|
||||||
|
default_lang = 'en'
|
||||||
|
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
cwd = sys.argv[1]
|
||||||
|
default_lang = sys.argv[2]
|
||||||
|
elif len(sys.argv) > 1:
|
||||||
|
default_lang = sys.argv[1]
|
||||||
|
|
||||||
|
update_languages(cwd, default_lang)
|
||||||
@@ -14,7 +14,7 @@ def zip_static(filelist=[]):
|
|||||||
extension = os.path.splitext(fi)
|
extension = os.path.splitext(fi)
|
||||||
extension = len(extension) > 1 and extension[1] or None
|
extension = len(extension) > 1 and extension[1] or None
|
||||||
if not extension or extension not in ALLOWED_EXTS:
|
if not extension or extension not in ALLOWED_EXTS:
|
||||||
print 'skipping %s' % os.path.basename(fi)
|
print('skipping %s' % os.path.basename(fi))
|
||||||
continue
|
continue
|
||||||
fstats = os.stat(fi)
|
fstats = os.stat(fi)
|
||||||
atime, mtime = fstats.st_atime, fstats.st_mtime
|
atime, mtime = fstats.st_atime, fstats.st_mtime
|
||||||
@@ -23,10 +23,10 @@ def zip_static(filelist=[]):
|
|||||||
zstats = os.stat(gfi)
|
zstats = os.stat(gfi)
|
||||||
zatime, zmtime = zstats.st_atime, zstats.st_mtime
|
zatime, zmtime = zstats.st_atime, zstats.st_mtime
|
||||||
if zatime == atime and zmtime == mtime:
|
if zatime == atime and zmtime == mtime:
|
||||||
print 'skipping %s, already gzipped to the latest version' % os.path.basename(fi)
|
print('skipping %s, already gzipped to the latest version' % os.path.basename(fi))
|
||||||
continue
|
continue
|
||||||
print 'gzipping %s to %s' % (
|
print('gzipping %s to %s' % (
|
||||||
os.path.basename(fi), os.path.basename(gfi))
|
os.path.basename(fi), os.path.basename(gfi)))
|
||||||
f_in = open(fi, 'rb')
|
f_in = open(fi, 'rb')
|
||||||
f_out = gzip.open(gfi, 'wb')
|
f_out = gzip.open(gfi, 'wb')
|
||||||
f_out.writelines(f_in)
|
f_out.writelines(f_in)
|
||||||
@@ -36,7 +36,7 @@ def zip_static(filelist=[]):
|
|||||||
saved = fstats.st_size - os.stat(gfi).st_size
|
saved = fstats.st_size - os.stat(gfi).st_size
|
||||||
tsave += saved
|
tsave += saved
|
||||||
|
|
||||||
print 'saved %s KB' % (int(tsave) / 1000.0)
|
print('saved %s KB' % (int(tsave) / 1000.0))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ALLOWED_EXTS = ['.css', '.js']
|
ALLOWED_EXTS = ['.css', '.js']
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ def start():
|
|||||||
'gluon/contrib/pysimplesoap',
|
'gluon/contrib/pysimplesoap',
|
||||||
'gluon/contrib/pg8000',
|
'gluon/contrib/pg8000',
|
||||||
'gluon/contrib/plural_rules',
|
'gluon/contrib/plural_rules',
|
||||||
|
'gluon/contrib/minify',
|
||||||
|
'gluon/contrib/pyaes',
|
||||||
|
'gluon/contrib/pyuca',
|
||||||
'gluon/tests',
|
'gluon/tests',
|
||||||
],
|
],
|
||||||
package_data={'gluon': ['env.tar']},
|
package_data={'gluon': ['env.tar']},
|
||||||
|
|||||||
Reference in New Issue
Block a user