Compare commits
368 Commits
mdipierro-
...
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 | ||
|
|
83681f3f5d | ||
|
|
b0a01ef720 | ||
|
|
da5543f62e | ||
|
|
dc4ff7c3cc | ||
|
|
0223b0871e | ||
|
|
b3a7c20f3f | ||
|
|
2fc4115718 | ||
|
|
c6c027dbec | ||
|
|
60edb11420 | ||
|
|
51ce3ffd36 | ||
|
|
89832479fc | ||
|
|
15ffdd9a20 | ||
|
|
8505c7b282 | ||
|
|
5583e9cdc7 | ||
|
|
5d8a25626c | ||
|
|
9ded289924 | ||
|
|
f657b42f65 | ||
|
|
1c0b498880 | ||
|
|
2fd0c7c778 | ||
|
|
3d0b81cbe6 | ||
|
|
48d69e9724 | ||
|
|
757ce4e76e | ||
|
|
af7bfac1e2 | ||
|
|
054320820c | ||
|
|
1a52b0ee3b | ||
|
|
78f3af6fc1 | ||
|
|
b5b98d6e19 | ||
|
|
aa1b71e431 | ||
|
|
472c0ff2fb | ||
|
|
58bedd4c1a | ||
|
|
88790dcaee | ||
|
|
a8fb41333b | ||
|
|
8d5464692f | ||
|
|
2080e0460f | ||
|
|
7f5fc798c5 | ||
|
|
833cb03ee1 | ||
|
|
590de9c890 | ||
|
|
583d106104 | ||
|
|
7ada2cf89a | ||
|
|
2f0b429f9e | ||
|
|
9f79dccb05 | ||
|
|
a78662e4cc | ||
|
|
81fa787ec2 | ||
|
|
159dd0d022 | ||
|
|
16df6840ed | ||
|
|
0674111129 | ||
|
|
18b755b8da | ||
|
|
2174fc3bec | ||
|
|
77a947b35c | ||
|
|
954cef48da | ||
|
|
566feb79d4 | ||
|
|
c117e3d1b9 | ||
|
|
0c3662fb6d | ||
|
|
2ea5939640 | ||
|
|
7c9653ae23 | ||
|
|
a02538549b | ||
|
|
ce965cf62a | ||
|
|
2a33c0faff | ||
|
|
ac67beb280 | ||
|
|
617ca4a98d | ||
|
|
85ecebc3a4 | ||
|
|
376c12a225 | ||
|
|
7769917102 | ||
|
|
30c28ad44c | ||
|
|
d905968197 | ||
|
|
f1ef95e15f | ||
|
|
da6688360d | ||
|
|
f30c31a8a2 | ||
|
|
59405b9f18 | ||
|
|
1c747357b0 | ||
|
|
6342bf0ddb | ||
|
|
f76437703b | ||
|
|
6f256be1f1 | ||
|
|
3505e372d8 | ||
|
|
49bf14e79a | ||
|
|
cf1ea98217 | ||
|
|
8a741023d8 | ||
|
|
1f4a490a84 | ||
|
|
ca5539561f | ||
|
|
57b554d618 | ||
|
|
69e6e79e23 | ||
|
|
baa129f871 | ||
|
|
90e606dcfd | ||
|
|
1d77968a06 | ||
|
|
1842a2e42c | ||
|
|
c9b6b0faf8 | ||
|
|
d8be963656 | ||
|
|
496112c4fe | ||
|
|
a186f5c51f | ||
|
|
fef43cd053 | ||
|
|
ab41cd94ec | ||
|
|
9ab7ed0029 | ||
|
|
4d117af85f | ||
|
|
60a6180a77 | ||
|
|
3292f760ca | ||
|
|
ab537242c4 | ||
|
|
02d2fefc21 | ||
|
|
232598fd8d | ||
|
|
7a69e087f7 | ||
|
|
6da3a9d8fd | ||
|
|
fe0f506efc | ||
|
|
140023e920 | ||
|
|
ad43249f61 | ||
|
|
dd31fb480c | ||
|
|
e5121876db | ||
|
|
532137bce5 | ||
|
|
35f7caa2f0 | ||
|
|
98dddee697 | ||
|
|
5000c47472 | ||
|
|
bf3d53ad96 | ||
|
|
b2f221bbaa | ||
|
|
868d6f6369 | ||
|
|
0e1831bcc7 | ||
|
|
dda808ebda | ||
|
|
a81e116274 | ||
|
|
34f2825a49 | ||
|
|
7dec909254 | ||
|
|
ca9198a26e | ||
|
|
ad421e42c6 | ||
|
|
6954988851 | ||
|
|
f0382d646c | ||
|
|
48a0683dd1 | ||
|
|
d7fb270e7e | ||
|
|
86a2c529b9 | ||
|
|
55592e7c6e | ||
|
|
a23b264a37 | ||
|
|
79e256b1d7 | ||
|
|
920ab72415 | ||
|
|
757d46274e | ||
|
|
7b66ec0ae3 | ||
|
|
bf5ec0d7cf | ||
|
|
85c68e6876 | ||
|
|
d1dfc4a06a | ||
|
|
02f0bdb8d3 |
17
.travis.yml
17
.travis.yml
@@ -4,10 +4,22 @@ sudo: required
|
||||
|
||||
cache: pip
|
||||
|
||||
dist: "trusty"
|
||||
|
||||
python:
|
||||
- '2.7'
|
||||
- 'pypy'
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.6-dev'
|
||||
- '3.7-dev'
|
||||
- 'pypy-5.3.1'
|
||||
- 'pypy3.5-5.7.1-beta'
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: 'pypy3.5-5.7.1-beta'
|
||||
- python: '3.6-dev'
|
||||
- python: '3.7-dev'
|
||||
|
||||
install:
|
||||
- pip install -e .
|
||||
@@ -32,3 +44,6 @@ notifications:
|
||||
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
apt:
|
||||
packages:
|
||||
- postgresql-9.4-postgis-2.3
|
||||
|
||||
50
CHANGELOG
50
CHANGELOG
@@ -1,8 +1,36 @@
|
||||
## 2.16.1
|
||||
- pydal 17.11
|
||||
- bootstrap 4
|
||||
- better welcome examples
|
||||
- many bug fixes
|
||||
|
||||
## 2.15.x
|
||||
- web2py does not support python 2.6 anymore
|
||||
- py3.5 syntax compatible (see #1353 for details)
|
||||
- dropped web shell from admin
|
||||
## 2.15.1-4
|
||||
- pydal 17.08
|
||||
- dropped support for python 2.6
|
||||
- dropped web shell
|
||||
- experimental python 3 support
|
||||
- experimental authapi for service login
|
||||
- allow ajax file uploads
|
||||
- more tests
|
||||
- more pep8 compliance
|
||||
- d3.js model visulization
|
||||
- improved scheduler
|
||||
- is_email support for internationalized Domain Names
|
||||
- improved used of cookies with CookieJar
|
||||
- SQLFORM.grid(showblobs=True)
|
||||
- import JS events (added w2p.componentBegin event)
|
||||
- added support for CASv3
|
||||
- allow first_name and last_name placeholders in verify_email message
|
||||
- added three-quote support in markmin
|
||||
- updated pg8000 driver (but we still recommend psycopg2)
|
||||
- compiled views use . separator not _ separator (must recompile code)
|
||||
- better serbian, french, and catalan translations
|
||||
- speed improvements (refactor of compileapp and pyc caching)
|
||||
- removed web shell (never worked as intended)
|
||||
- allow Expose(..., follow_symlink_out=False).
|
||||
- Updated fpdf to latest version
|
||||
- JWT support
|
||||
- import fabfile for remote deployment
|
||||
- scheduler new feature: you can now specify intervals with cron
|
||||
- gluon/* removed from sys.path. Applications relying on statements like e.g.
|
||||
"from storage import Storage"
|
||||
@@ -10,8 +38,18 @@
|
||||
"from gluon.storage import Storage"
|
||||
- tests can only be run with the usual web2py.py --run_system_tests OR with
|
||||
python -m unittest -v gluon.tests on the root dir
|
||||
- updated pymysql driver
|
||||
|
||||
- jQuery 3.2.1
|
||||
- PyDAL 17.07 including:
|
||||
allow jsonb support for postgres
|
||||
correctly configure adapters that need connection for configuration
|
||||
better caching
|
||||
updated IMAP adapter methods to new API
|
||||
experimental suport for joinable subselects
|
||||
improved Teradata support
|
||||
improved mongodb support
|
||||
overall refactoring
|
||||
experimental support for Google Cloud SQL v2
|
||||
new pymysql driver
|
||||
|
||||
## 2.14.6
|
||||
|
||||
|
||||
16
Makefile
16
Makefile
@@ -30,11 +30,7 @@ update:
|
||||
wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py
|
||||
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
|
||||
echo "remember that pymysql was tweaked"
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
make clean
|
||||
rmfiles:
|
||||
### clean up baisc apps
|
||||
rm -f routes.py
|
||||
rm -rf applications/*/sessions/*
|
||||
@@ -46,6 +42,12 @@ src:
|
||||
rm -rf applications/admin/uploads/*
|
||||
rm -rf applications/welcome/uploads/*
|
||||
rm -rf applications/examples/uploads/*
|
||||
src:
|
||||
### Use semantic versioning
|
||||
echo 'Version 2.16.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||
### rm -f all junk files
|
||||
#make clean
|
||||
# make rmfiles
|
||||
### make welcome layout and appadmin the default
|
||||
cp applications/welcome/views/appadmin.html applications/admin/views
|
||||
cp applications/welcome/views/appadmin.html applications/examples/views
|
||||
@@ -54,7 +56,7 @@ src:
|
||||
### build web2py_src.zip
|
||||
echo '' > NEWINSTALL
|
||||
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
||||
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||
cd ..; zip -r --exclude=**.git** web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||
|
||||
mdp:
|
||||
make src
|
||||
@@ -97,6 +99,8 @@ win:
|
||||
cp -r applications/welcome ../web2py_win/web2py/applications
|
||||
cp -r applications/examples ../web2py_win/web2py/applications
|
||||
cp applications/__init__.py ../web2py_win/web2py/applications
|
||||
# per https://github.com/web2py/web2py/issues/1716
|
||||
mv ../web2py_win/web2py/_ssl.pyd ../web2py_win/web2py/_ssl.pyd.legacy | echo 'done'
|
||||
cd ../web2py_win; zip -r web2py_win.zip web2py
|
||||
mv ../web2py_win/web2py_win.zip .
|
||||
run:
|
||||
|
||||
2
VERSION
2
VERSION
@@ -1 +1 @@
|
||||
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48
|
||||
Version 2.16.1-stable+timestamp.2017.11.13.23.50.07
|
||||
|
||||
@@ -12,11 +12,6 @@ import gluon.contenttype
|
||||
import gluon.fileutils
|
||||
from gluon._compat import iteritems
|
||||
|
||||
try:
|
||||
import pygraphviz as pgv
|
||||
except ImportError:
|
||||
pgv = None
|
||||
|
||||
is_gae = request.env.web2py_runtime_gae or False
|
||||
|
||||
# ## critical --- make a copy of the environment
|
||||
@@ -565,57 +560,6 @@ def table_template(table):
|
||||
_cellborder=0, _cellspacing=0)
|
||||
).xml()
|
||||
|
||||
|
||||
def bg_graph_model():
|
||||
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
|
||||
|
||||
subgraphs = dict()
|
||||
for tablename in db.tables:
|
||||
if hasattr(db[tablename],'_meta_graphmodel'):
|
||||
meta_graphmodel = db[tablename]._meta_graphmodel
|
||||
else:
|
||||
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
||||
|
||||
group = meta_graphmodel['group'].replace(' ', '')
|
||||
if group not in subgraphs:
|
||||
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
||||
subgraphs[group]['tables'].append(tablename)
|
||||
|
||||
graph.add_node(tablename, name=tablename, shape='plaintext',
|
||||
label=table_template(tablename))
|
||||
|
||||
for n, key in enumerate(subgraphs.iterkeys()):
|
||||
graph.subgraph(nbunch=subgraphs[key]['tables'],
|
||||
name='cluster%d' % n,
|
||||
style='filled',
|
||||
color=subgraphs[key]['meta']['color'],
|
||||
label=subgraphs[key]['meta']['group'])
|
||||
|
||||
for tablename in db.tables:
|
||||
for field in db[tablename]:
|
||||
f_type = field.type
|
||||
if isinstance(f_type,str) and (
|
||||
f_type.startswith('reference') or
|
||||
f_type.startswith('list:reference')):
|
||||
referenced_table = f_type.split()[1].split('.')[0]
|
||||
n1 = graph.get_node(tablename)
|
||||
n2 = graph.get_node(referenced_table)
|
||||
graph.add_edge(n1, n2, color="#4C4C4C", label='')
|
||||
|
||||
graph.layout()
|
||||
if not request.args:
|
||||
response.headers['Content-Type'] = 'image/png'
|
||||
return graph.draw(format='png', prog='dot')
|
||||
else:
|
||||
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
|
||||
if request.args(0) == 'dot':
|
||||
return graph.string()
|
||||
else:
|
||||
return graph.draw(format=request.args(0), prog='dot')
|
||||
|
||||
def graph_model():
|
||||
return dict(databases=databases, pgv=pgv)
|
||||
|
||||
def manage():
|
||||
tables = manager_action['tables']
|
||||
if isinstance(tables[0], str):
|
||||
@@ -700,3 +644,51 @@ def hooks():
|
||||
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
|
||||
ul_main.append(ul_t)
|
||||
return ul_main
|
||||
|
||||
|
||||
# ##########################################################
|
||||
# d3 based model visualizations
|
||||
# ###########################################################
|
||||
|
||||
def d3_graph_model():
|
||||
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
|
||||
and also the app_admin bg_graph_model function
|
||||
|
||||
Create a list of table dicts, called "nodes"
|
||||
"""
|
||||
|
||||
nodes = []
|
||||
links = []
|
||||
|
||||
for database in databases:
|
||||
db = eval_in_global_env(database)
|
||||
for tablename in db.tables:
|
||||
fields = []
|
||||
for field in db[tablename]:
|
||||
f_type = field.type
|
||||
if not isinstance(f_type,str):
|
||||
disp = ' '
|
||||
elif f_type == 'string':
|
||||
disp = field.length
|
||||
elif f_type == 'id':
|
||||
disp = "PK"
|
||||
elif f_type.startswith('reference') or \
|
||||
f_type.startswith('list:reference'):
|
||||
disp = "FK"
|
||||
else:
|
||||
disp = ' '
|
||||
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
||||
|
||||
if isinstance(f_type,str) and (
|
||||
f_type.startswith('reference') or
|
||||
f_type.startswith('list:reference')):
|
||||
referenced_table = f_type.split()[1].split('.')[0]
|
||||
|
||||
links.append(dict(source=tablename, target = referenced_table))
|
||||
|
||||
nodes.append(dict(name=tablename, type="table", fields = fields))
|
||||
|
||||
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
||||
response.files.append(URL('admin','static','js/d3.min.js'))
|
||||
response.files.append(URL('admin','static','js/d3_graph.js'))
|
||||
return dict(databases=databases, nodes=nodes, links=links)
|
||||
|
||||
@@ -6,6 +6,7 @@ import gluon.validators
|
||||
import code
|
||||
from gluon.debug import communicate, web_debugger, dbg_debugger
|
||||
from gluon._compat import thread
|
||||
from gluon.fileutils import open_file
|
||||
import pydoc
|
||||
|
||||
|
||||
@@ -54,7 +55,7 @@ def interact():
|
||||
if filename:
|
||||
# prevent IOError 2 on some circuntances (EAFP instead of os.access)
|
||||
try:
|
||||
lines = open(filename).readlines()
|
||||
lines = open_file(filename, 'r').readlines()
|
||||
except:
|
||||
lines = ""
|
||||
lines = dict([(i + 1, l) for (i, l) in enumerate(
|
||||
|
||||
@@ -16,6 +16,7 @@ from gluon.tools import Config
|
||||
from gluon.compileapp import find_exposed_functions
|
||||
from glob import glob
|
||||
from gluon._compat import iteritems, PY2, pickle, xrange, urlopen, to_bytes, StringIO, to_native
|
||||
import gluon.rewrite
|
||||
import shutil
|
||||
import platform
|
||||
|
||||
@@ -249,6 +250,7 @@ def site():
|
||||
db.app.insert(name=appname, owner=auth.user.id)
|
||||
log_progress(appname)
|
||||
session.flash = T('new application "%s" created', appname)
|
||||
gluon.rewrite.load()
|
||||
redirect(URL('design', args=appname))
|
||||
else:
|
||||
session.flash = \
|
||||
@@ -266,6 +268,7 @@ def site():
|
||||
new_repo = git.Repo.clone_from(form_update.vars.url, target)
|
||||
session.flash = T('new application "%s" imported',
|
||||
form_update.vars.name)
|
||||
gluon.rewrite.load()
|
||||
except git.GitCommandError as err:
|
||||
session.flash = T('Invalid git repository specified.')
|
||||
redirect(URL(r=request))
|
||||
@@ -302,6 +305,7 @@ def site():
|
||||
log_progress(appname)
|
||||
session.flash = T(msg, dict(appname=appname,
|
||||
digest=md5_hash(installed)))
|
||||
gluon.rewrite.load()
|
||||
else:
|
||||
msg = 'unable to install application "%(appname)s"'
|
||||
session.flash = T(msg, dict(appname=form_update.vars.name))
|
||||
@@ -959,8 +963,7 @@ def edit_language():
|
||||
form = SPAN(strings['__corrupted__'], _class='error')
|
||||
return dict(filename=filename, form=form)
|
||||
|
||||
keys = sorted(strings.keys(), lambda x, y: cmp(
|
||||
unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower()))
|
||||
keys = sorted(strings.keys(), key=lambda x: to_native(x).lower())
|
||||
rows = []
|
||||
rows.append(H2(T('Original/Translation')))
|
||||
|
||||
@@ -1861,7 +1864,6 @@ def user():
|
||||
|
||||
def reload_routes():
|
||||
""" Reload routes.py """
|
||||
import gluon.rewrite
|
||||
gluon.rewrite.load()
|
||||
redirect(URL('site'))
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
import gzip
|
||||
import tarfile
|
||||
from gluon.contrib.simplejsonrpc import ServerProxy
|
||||
from gluon._compat import StringIO, ProtocolError
|
||||
from gluon._compat import StringIO, ProtocolError, urlencode, urllib2
|
||||
|
||||
def deploy():
|
||||
response.title = T('Deploy to pythonanywhere')
|
||||
@@ -26,9 +26,8 @@ def create_account():
|
||||
except ProtocolError as error:
|
||||
pass
|
||||
|
||||
import urllib, urllib2
|
||||
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
|
||||
data = urllib.urlencode(request.vars)
|
||||
data = urlencode(request.vars)
|
||||
req = urllib2.Request(url, data)
|
||||
|
||||
try:
|
||||
|
||||
@@ -21,18 +21,19 @@
|
||||
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
|
||||
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
|
||||
'?': '?',
|
||||
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||
'@markmin\x01Searching: **%s** %%{file}': 'Cherche: **%s** fichiers',
|
||||
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
|
||||
'A new version of web2py is available: %s': 'Une nouvelle version de web2py est disponible: %s ',
|
||||
'A new version of web2py is available: Version 1.68.2 (2009-10-21 09:59:29)\n': 'Une nouvelle version de web2py est disponible: Version 1.68.2 (2009-10-21 09:59:29)\r\n',
|
||||
'Abort': 'Abort',
|
||||
'About': 'à propos',
|
||||
'About application': "A propos de l'application",
|
||||
'About': 'À propos',
|
||||
'About application': "À propos de l'application",
|
||||
'Accept Terms': 'Termes acceptés',
|
||||
'Add breakpoint': 'Ajouter une interruption',
|
||||
'additional code for your application': 'code supplémentaire pour votre application',
|
||||
'Additional code for your application': 'Code additionnel pour votre application',
|
||||
'Admin design page': 'Admin design page',
|
||||
'Admin design page': 'Page de conception admin',
|
||||
'admin disabled because no admin password': 'admin désactivée car aucun mot de passe admin',
|
||||
'admin disabled because not supported on google app engine': 'admin désactivée car non prise en charge sur Google Apps engine',
|
||||
'admin disabled because too many invalid login attempts': 'admin disabled because too many invalid login attempts',
|
||||
@@ -41,7 +42,7 @@
|
||||
'Admin language': "Language de l'admin",
|
||||
'Admin versioning page': 'Admin versioning page',
|
||||
'administrative interface': "interface d'administration",
|
||||
'Administrator Password:': 'Mot de passe Administrateur:',
|
||||
'Administrator Password:': "Mot de passe de l'administrateur:",
|
||||
'An error occured, please [[reload %s]] the page': 'Une erreur c’est produite, s’il vous plait [[reload %s]] la page',
|
||||
'and rename it (required):': 'et renommez-la (obligatoire):',
|
||||
'and rename it:': 'et renommez-le:',
|
||||
@@ -57,14 +58,14 @@
|
||||
'application is compiled and cannot be designed': "l'application est compilée et ne peut être modifiée",
|
||||
'Application name:': "Nom de l'application:",
|
||||
'Application updated via git pull': 'Application updated via git pull',
|
||||
'are not used': 'are not used',
|
||||
'are not used yet': 'are not used yet',
|
||||
'are not used': 'ne sont pas utilisé',
|
||||
'are not used yet': 'ne sont pas encore utilisé',
|
||||
'Are you sure you want to delete file "%s"?': 'Êtes-vous sûr de vouloir supprimer le fichier «%s»?',
|
||||
'Are you sure you want to delete plugin "%s"?': 'Êtes-vous sûr de vouloir supprimer le plugin "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||
'Are you sure you want to uninstall application "%s"?': "Êtes-vous sûr de vouloir désinstaller l'application «%s»?",
|
||||
'Are you sure you want to upgrade web2py now?': 'Êtes-vous sûr de vouloir mettre à jour web2py maintenant?',
|
||||
'Are you sure?': 'Etes vous sûr?',
|
||||
'Are you sure?': 'Êtes vous sûr?',
|
||||
'arguments': 'arguments',
|
||||
'at char %s': 'at char %s',
|
||||
'at line %s': 'at line %s',
|
||||
@@ -73,13 +74,13 @@
|
||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: les tests ne sont pas thread-safe DONC NE PAS EFFECTUER DES TESTS MULTIPLES SIMULTANÉMENT.',
|
||||
'ATTENTION: you cannot edit the running application!': "ATTENTION: vous ne pouvez pas modifier l'application qui tourne!",
|
||||
'Autocomplete Python Code': 'Autocomplete Python Code',
|
||||
'Available databases and tables': 'Bases de données et tables disponible',
|
||||
'Available databases and tables': 'Bases de données et tables disponibles',
|
||||
'Available Databases and Tables': 'Available Databases and Tables',
|
||||
'back': 'retour',
|
||||
'Back to the plugins list': 'Retour à la liste de plugins',
|
||||
'Back to wizard': 'Back to wizard',
|
||||
'Basics': 'Basics',
|
||||
'Begin': 'Begin',
|
||||
'Begin': 'Début',
|
||||
'breakpoint': 'breakpoint',
|
||||
'Breakpoints': 'Breakpoints',
|
||||
'breakpoints': 'breakpoints',
|
||||
@@ -92,7 +93,7 @@
|
||||
'Cache Keys': 'Cache Keys',
|
||||
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyés',
|
||||
'can be a git repo': 'can be a git repo',
|
||||
'Cancel': 'Retour',
|
||||
'Cancel': 'Annuler',
|
||||
'Cannot be empty': 'Ne peut pas être vide',
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Ne peut pas compiler: il y a des erreurs dans votre application. corriger les erreurs et essayez à nouveau.',
|
||||
'Cannot compile: there are errors in your app:': 'Ne peut pas compiler: il y a des erreurs dans votre application:',
|
||||
@@ -102,60 +103,60 @@
|
||||
'Change admin password': 'Changer le mot de passe admin',
|
||||
'change editor settings': 'change editor settings',
|
||||
'Changelog': 'Changelog',
|
||||
'check all': 'tout vérifier ',
|
||||
'check all': 'tout sélectionner',
|
||||
'Check for upgrades': 'Vérifier les mises à jour',
|
||||
'Check to delete': 'Cocher pour supprimer',
|
||||
'Checking for upgrades...': 'Vérification des mises à jour ... ',
|
||||
'Clean': 'nettoyer',
|
||||
'Clear': 'Clear',
|
||||
'Clear CACHE?': 'Clear CACHE?',
|
||||
'Clear DISK': 'Clear DISK',
|
||||
'Clear RAM': 'Clear RAM',
|
||||
'Clear': 'Effacer',
|
||||
'Clear CACHE?': 'Effacer le CACHE?',
|
||||
'Clear DISK': 'Effacer le DISQUE',
|
||||
'Clear RAM': 'Effacer la RAM',
|
||||
'Click row to expand traceback': 'Click row to expand traceback',
|
||||
'Click row to view a ticket': 'Click row to view a ticket',
|
||||
'click to check for upgrades': 'Cliquez pour vérifier les mises jour',
|
||||
'code': 'code',
|
||||
'Code listing': 'Code listing',
|
||||
'collapse/expand all': 'tout réduire/agrandir',
|
||||
'Command': 'Command',
|
||||
'Comment:': 'Comment:',
|
||||
'Commit': 'Commit',
|
||||
'Commit form': 'Commit form',
|
||||
'Committed files': 'Committed files',
|
||||
'Command': 'Commande',
|
||||
'Comment:': 'Commentaire:',
|
||||
'Commit': 'Valider',
|
||||
'Commit form': 'Valider le formulaire',
|
||||
'Committed files': 'Fichiers validés',
|
||||
'Compile': 'compiler',
|
||||
'Compile (all or nothing)': 'Compile (all or nothing)',
|
||||
'Compile (skip failed views)': 'Compile (skip failed views)',
|
||||
'compiled application removed': 'application compilée enlevée',
|
||||
'Condition': 'Condition',
|
||||
'continue': 'continue',
|
||||
'continue': 'continuer',
|
||||
'Controllers': 'Contrôleurs',
|
||||
'controllers': 'contrôleurs',
|
||||
'Count': 'Count',
|
||||
'Count': 'Compte',
|
||||
'Create': 'Créer',
|
||||
'create file with filename:': 'créer un fichier avec nom de fichier:',
|
||||
'create new application:': 'créer une nouvelle application:',
|
||||
'Create new simple application': 'Créer une nouvelle application',
|
||||
'Create/Upload': 'Create/Upload',
|
||||
'created by': 'créé par',
|
||||
'Created by:': 'Created by:',
|
||||
'Created On': 'Created On',
|
||||
'Created on:': 'Created on:',
|
||||
'Created by:': 'Créé par:',
|
||||
'Created On': 'Créé le',
|
||||
'Created on:': 'Créé le:',
|
||||
'crontab': 'crontab',
|
||||
'Current request': 'Requête actuelle',
|
||||
'Current response': 'Réponse actuelle',
|
||||
'Current session': 'Session en cours',
|
||||
'currently running': 'tourne actuellement',
|
||||
'currently saved or': 'actuellement enregistré ou',
|
||||
'data uploaded': 'données chargées',
|
||||
'Database': 'Database',
|
||||
'data uploaded': 'données téléversées',
|
||||
'Database': 'Base de données',
|
||||
'database': 'base de données',
|
||||
'Database %s select': 'Database %s select',
|
||||
'database %s select': 'base de données %s sélectionner',
|
||||
'Database administration': 'Database administration',
|
||||
'database %s select': 'base de données %s sélectionner',
|
||||
'Database administration': 'Administration base de données',
|
||||
'database administration': 'administration base de données',
|
||||
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
||||
'Date and Time': 'Date et heure',
|
||||
'db': 'bdd',
|
||||
'db': 'bd',
|
||||
'Debug': 'Debug',
|
||||
'defines tables': 'définit les tables',
|
||||
'Delete': 'Supprimer',
|
||||
@@ -165,7 +166,7 @@
|
||||
'Delete this file (you will be asked to confirm deletion)': 'Supprimer ce fichier (on vous demandera de confirmer la suppression)',
|
||||
'Delete:': 'Supprimer:',
|
||||
'deleted after first hit': 'deleted after first hit',
|
||||
'Demo': 'Demo',
|
||||
'Demo': 'Démo',
|
||||
'Deploy': 'Déployer',
|
||||
'Deploy on Google App Engine': 'Déployer sur Google App Engine',
|
||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||
@@ -176,22 +177,22 @@
|
||||
'Description:': 'Description:',
|
||||
'design': 'conception',
|
||||
'Detailed traceback description': 'Detailed traceback description',
|
||||
'details': 'details',
|
||||
'details': 'détails',
|
||||
'direction: ltr': 'direction: ltr',
|
||||
'directory not found': 'directory not found',
|
||||
'Disable': 'Disable',
|
||||
'Disabled': 'Disabled',
|
||||
'Disable': 'Désactiver',
|
||||
'Disabled': 'Désactivé',
|
||||
'disabled in demo mode': 'disabled in demo mode',
|
||||
'disabled in GAE mode': 'disabled in GAE mode',
|
||||
'disabled in multi user mode': 'disabled in multi user mode',
|
||||
'DISK': 'DISK',
|
||||
'DISK': 'DISQUE',
|
||||
'Disk Cache Keys': 'Disk Cache Keys',
|
||||
'Disk Cleared': 'Disk Cleared',
|
||||
'Disk Cleared': 'Disque effacé',
|
||||
'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
|
||||
'Display line numbers': 'Display line numbers',
|
||||
'DO NOT use the "Pack compiled" feature.': 'DO NOT use the "Pack compiled" feature.',
|
||||
'docs': 'docs',
|
||||
'Docs': 'Docs',
|
||||
'docs': 'documents',
|
||||
'Docs': 'Documents',
|
||||
'done!': 'fait!',
|
||||
'Downgrade': 'Downgrade',
|
||||
'Download .w2p': 'Download .w2p',
|
||||
@@ -201,43 +202,43 @@
|
||||
'download plugins': 'télécharger plugins',
|
||||
'Download plugins from repository': 'Download plugins from repository',
|
||||
'EDIT': 'MODIFIER',
|
||||
'Edit': 'modifier',
|
||||
'edit all': 'edit all',
|
||||
'Edit': 'Modifier',
|
||||
'edit all': 'tout modifier',
|
||||
'Edit application': "Modifier l'application",
|
||||
'edit controller': 'modifier contrôleur',
|
||||
'edit controller:': 'edit controller:',
|
||||
'Edit current record': 'Modifier cette entrée',
|
||||
'edit views:': 'modifier vues:',
|
||||
'Editing %s': 'Editing %s',
|
||||
'edit controller:': 'modifier le contrôleur:',
|
||||
'Edit current record': 'Modifier cet enregistrement',
|
||||
'edit views:': 'modifier les vues:',
|
||||
'Editing %s': 'Modifier %s',
|
||||
'Editing file': 'Modifier le fichier',
|
||||
'Editing file "%s"': 'Modifier le fichier "% s" ',
|
||||
'Editing Language file': 'Modifier le fichier de langue',
|
||||
'Editing Plural Forms File': 'Editing Plural Forms File',
|
||||
'Editor': 'Editor',
|
||||
'Email Address': 'Email Address',
|
||||
'Enable': 'Enable',
|
||||
'Editing Plural Forms File': 'Modifier le fichier du formulaire pluriel',
|
||||
'Editor': 'Éditeur',
|
||||
'Email Address': 'Adresse courriel',
|
||||
'Enable': 'Activer',
|
||||
'Enable Close-Tag': 'Enable Close-Tag',
|
||||
'Enable Code Folding': 'Enable Code Folding',
|
||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||
'Error': 'Error',
|
||||
'Error logs for "%(app)s"': 'Journal d\'erreurs pour "%(app)s"',
|
||||
'Error snapshot': 'Error snapshot',
|
||||
'Error ticket': 'Error ticket',
|
||||
'Errors': 'erreurs',
|
||||
'Error ticket': "Billet d'erreur",
|
||||
'Errors': 'Erreurs',
|
||||
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
|
||||
'Exception %s': 'Exception %s',
|
||||
'Exception instance attributes': "Attributs d'instance Exception",
|
||||
'Exit Fullscreen': 'Exit Fullscreen',
|
||||
'Expand Abbreviation (html files only)': 'Expand Abbreviation (html files only)',
|
||||
'export as csv file': 'export au format CSV',
|
||||
'Exports:': 'Exports:',
|
||||
'Exports:': 'Exportions:',
|
||||
'exposes': 'expose',
|
||||
'exposes:': 'expose:',
|
||||
'extends': 'étend',
|
||||
'failed to compile file because:': 'failed to compile file because:',
|
||||
'failed to reload module': 'impossible de recharger le module',
|
||||
'failed to reload module because:': 'impossible de recharger le module car:',
|
||||
'File': 'File',
|
||||
'File': 'Fichier',
|
||||
'file "%(filename)s" created': 'fichier "%(filename)s" créé',
|
||||
'file "%(filename)s" deleted': 'fichier "%(filename)s" supprimé',
|
||||
'file "%(filename)s" uploaded': 'fichier "%(filename)s" chargé',
|
||||
@@ -247,19 +248,19 @@
|
||||
'file not found': 'file not found',
|
||||
'file saved on %(time)s': 'fichier enregistré le %(time)s',
|
||||
'file saved on %s': 'fichier enregistré le %s',
|
||||
'filename': 'filename',
|
||||
'Filename': 'Filename',
|
||||
'Files added': 'Files added',
|
||||
'filename': 'nom de fichier',
|
||||
'Filename': 'Nom de fichier',
|
||||
'Files added': 'Fichiers ajoutés',
|
||||
'filter': 'filtre',
|
||||
'Find Next': 'Find Next',
|
||||
'Find Previous': 'Find Previous',
|
||||
'Form has errors': 'Form has errors',
|
||||
'Find Next': 'Trouver les suivants',
|
||||
'Find Previous': 'Trouver les précédents',
|
||||
'Form has errors': 'Le formulaire comporte des erreurs',
|
||||
'Frames': 'Frames',
|
||||
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraîneront des tests [passed] .',
|
||||
'GAE Email': 'GAE Email',
|
||||
'GAE Output': 'GAE Output',
|
||||
'GAE Password': 'GAE Password',
|
||||
'Generate': 'Generate',
|
||||
'GAE Password': 'Mot de passe GAE',
|
||||
'Generate': 'Générer',
|
||||
'Git Pull': 'Git Pull',
|
||||
'Git Push': 'Git Push',
|
||||
'Globals##debug': 'Globals##debug',
|
||||
@@ -267,15 +268,15 @@
|
||||
'Google App Engine Deployment Interface': 'Google App Engine Deployment Interface',
|
||||
'Google Application Id': 'Google Application Id',
|
||||
'Goto': 'Goto',
|
||||
'graph model': 'graph model',
|
||||
'Graph Model': 'Graph Model',
|
||||
'graph model': 'représentation graphique du modèle',
|
||||
'Graph Model': 'Représentation graphique du modèle',
|
||||
'Help': 'aide',
|
||||
'here': 'here',
|
||||
'here': 'ici',
|
||||
'Hide/Show Translated strings': 'Hide/Show Translated strings',
|
||||
'Highlight current line': 'Highlight current line',
|
||||
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
|
||||
'Hits': 'Hits',
|
||||
'Home': 'Home',
|
||||
'Home': 'Accueil',
|
||||
'honored only if the expression evaluates to true': 'honored only if the expression evaluates to true',
|
||||
'htmledit': 'edition html',
|
||||
'If start the downgrade, be patient, it may take a while to rollback': 'If start the downgrade, be patient, it may take a while to rollback',
|
||||
@@ -283,7 +284,7 @@
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': "Si le rapport ci-dessus contient un numéro de ticket, cela indique une défaillance dans l'exécution du contrôleur, avant toute tentative d'exécuter les doctests. Cela est généralement dû à une erreur d'indentation ou une erreur à l'extérieur du code de la fonction.\r\nUn titre vert indique que tous les tests (si définis) sont passés. Dans ce cas, les résultats des essais ne sont pas affichées.",
|
||||
'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.',
|
||||
'import': 'import',
|
||||
'import': 'importer',
|
||||
'Import/Export': 'Importer/Exporter',
|
||||
'In development, use the default Rocket webserver that is currently supported by this debugger.': 'In development, use the default Rocket webserver that is currently supported by this debugger.',
|
||||
'includes': 'inclus',
|
||||
@@ -311,14 +312,14 @@
|
||||
'Invalid request': 'Invalid request',
|
||||
'invalid table names (auth_* tables already defined)': 'invalid table names (auth_* tables already defined)',
|
||||
'invalid ticket': 'ticket non valide',
|
||||
'Key': 'Key',
|
||||
'Key': 'Clé',
|
||||
'Keyboard shortcuts': 'Keyboard shortcuts',
|
||||
'kill process': 'kill process',
|
||||
'language file "%(filename)s" created/updated': 'fichier de langue "%(filename)s" créé/mis à jour',
|
||||
'Language files (static strings) updated': 'Fichiers de langue (chaînes statiques) mis à jour ',
|
||||
'languages': 'langues',
|
||||
'Languages': 'Langues',
|
||||
'Last Revision': 'Last Revision',
|
||||
'Last Revision': 'Dernière révision',
|
||||
'Last saved on:': 'Dernière sauvegarde le:',
|
||||
'License for': 'Licence pour',
|
||||
'License:': 'License:',
|
||||
@@ -326,8 +327,8 @@
|
||||
'Line number': 'Line number',
|
||||
'lists by exception': 'lists by exception',
|
||||
'lists by ticket': 'lists by ticket',
|
||||
'Loading...': 'Loading...',
|
||||
'loading...': 'Chargement ...',
|
||||
'Loading...': 'Chargement...',
|
||||
'loading...': 'chargement ...',
|
||||
'Local Apps': 'Local Apps',
|
||||
'locals': 'locals',
|
||||
'Locals##debug': 'Locals##debug',
|
||||
@@ -337,7 +338,7 @@
|
||||
'Login to the Administrative Interface': "Se connecter à l'interface d'administration",
|
||||
'Login/Register': 'Login/Register',
|
||||
'Logout': 'déconnexion',
|
||||
'lost password': 'lost password',
|
||||
'lost password': 'mot de passe perdu',
|
||||
'Main Menu': 'Main Menu',
|
||||
'Manage': 'Manage',
|
||||
'Manage %(action)s': 'Manage %(action)s',
|
||||
@@ -350,7 +351,7 @@
|
||||
'merge': 'fusionner',
|
||||
'Models': 'Modèles',
|
||||
'models': 'modèles',
|
||||
'Modified On': 'Modified On',
|
||||
'Modified On': 'Modifié le',
|
||||
'Modules': 'Modules',
|
||||
'modules': 'modules',
|
||||
'Multi User Mode': 'Multi User Mode',
|
||||
@@ -363,8 +364,8 @@
|
||||
'New Record': 'Nouvelle Entrée',
|
||||
'new record inserted': 'nouvelle entrée insérée',
|
||||
'New simple application': 'Nouvelle application simple',
|
||||
'next': 'next',
|
||||
'next %s rows': 'next %s rows',
|
||||
'next': 'suivant',
|
||||
'next %s rows': '%s lignes suivantes',
|
||||
'next 100 rows': '100 lignes suivantes',
|
||||
'NO': 'NON',
|
||||
'no changes': 'no changes',
|
||||
@@ -374,8 +375,8 @@
|
||||
'no package selected': 'no package selected',
|
||||
'no permission to uninstall "%s"': 'no permission to uninstall "%s"',
|
||||
'Node:': 'Node:',
|
||||
'Not Authorized': 'Not Authorized',
|
||||
'Not supported': 'Not supported',
|
||||
'Not Authorized': 'Pas autorisé',
|
||||
'Not supported': 'Pas supporté',
|
||||
'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.': 'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.',
|
||||
'Number of entries: **%s**': 'Number of entries: **%s**',
|
||||
"On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": "On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.",
|
||||
@@ -402,14 +403,14 @@
|
||||
'Peeking at file': 'Jeter un oeil au fichier',
|
||||
'Permission': 'Permission',
|
||||
'Permissions': 'Permissions',
|
||||
'Please': 'Please',
|
||||
'Please': 'SVP',
|
||||
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
|
||||
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" supprimé',
|
||||
'Plugin "%s" in application': 'Plugin "%s" dans l\'application',
|
||||
'plugin not specified': 'plugin not specified',
|
||||
'Plugin page': 'Plugin page',
|
||||
'plugins': 'plugins',
|
||||
'Plugins': 'Plugins',
|
||||
'plugins': 'plugiciels',
|
||||
'Plugins': 'Plugiciels',
|
||||
'Plural Form #%s': 'Plural Form #%s',
|
||||
'Plural-Forms:': 'Plural-Forms:',
|
||||
'Powered by': 'Propulsé par',
|
||||
@@ -423,7 +424,7 @@
|
||||
'Pull': 'Pull',
|
||||
'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull failed, certain files could not be checked out. Check logs for details.',
|
||||
'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.',
|
||||
'Push': 'Push',
|
||||
'Push': 'Pousser',
|
||||
'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.',
|
||||
'pygraphviz library not found': 'pygraphviz library not found',
|
||||
'PythonAnywhere Apps': 'PythonAnywhere Apps',
|
||||
@@ -433,9 +434,9 @@
|
||||
'RAM Cache Keys': 'RAM Cache Keys',
|
||||
'Ram Cleared': 'Ram Cleared',
|
||||
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
|
||||
'Rapid Search': 'Rapid Search',
|
||||
'Record': 'Record',
|
||||
'record': 'entrée',
|
||||
'Rapid Search': 'Recherche rapide',
|
||||
'Record': 'Enregistrement',
|
||||
'record': 'enregistrement',
|
||||
'record does not exist': "l'entrée n'existe pas",
|
||||
'record id': 'id entrée',
|
||||
'Record id': 'Record id',
|
||||
@@ -444,24 +445,24 @@
|
||||
'Reload routes': 'Reload routes',
|
||||
'Remove compiled': 'retirer compilé',
|
||||
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
|
||||
'Replace': 'Replace',
|
||||
'Replace All': 'Replace All',
|
||||
'Replace': 'Remplacer',
|
||||
'Replace All': 'Tout remplacer',
|
||||
'Repository (%s)': 'Repository (%s)',
|
||||
'request': 'request',
|
||||
'requires distutils, but not installed': 'requires distutils, but not installed',
|
||||
'requires python-git, but not installed': 'requires python-git, but not installed',
|
||||
'Resolve Conflict file': 'Résoudre les conflits de fichiers',
|
||||
'response': 'response',
|
||||
'restart': 'restart',
|
||||
'restart': 'redémarrer',
|
||||
'restore': 'restaurer',
|
||||
'return': 'return',
|
||||
'Revert': 'Revert',
|
||||
'revert': 'revenir',
|
||||
'return': 'retour',
|
||||
'Revert': "Revenir vers l'arrière",
|
||||
'revert': "revenir vers l'arrière",
|
||||
'reverted to revision %s': 'reverted to revision %s',
|
||||
'Revision %s': 'Revision %s',
|
||||
'Revision:': 'Revision:',
|
||||
'Role': 'Role',
|
||||
'Roles': 'Roles',
|
||||
'Revision %s': 'Révision %s',
|
||||
'Revision:': 'Révision:',
|
||||
'Role': 'Rôle',
|
||||
'Roles': 'Rôles',
|
||||
'Rows in Table': 'Rows in Table',
|
||||
'Rows in table': 'Lignes de la table',
|
||||
'Rows selected': 'Lignes sélectionnées',
|
||||
@@ -470,15 +471,15 @@
|
||||
'Run tests in this file': 'Run tests in this file',
|
||||
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Lancer les tests dans ce fichier (pour lancer tous les fichiers, vous pouvez également utiliser le bouton nommé 'test')",
|
||||
'Running on %s': 'Running on %s',
|
||||
'Save': 'Enregistrer',
|
||||
'save': 'sauver',
|
||||
'Save file:': 'Save file:',
|
||||
'Save file: %s': 'Save file: %s',
|
||||
'Save model as...': 'Save model as...',
|
||||
'Save via Ajax': 'Save via Ajax',
|
||||
'Save': 'Sauvegarder',
|
||||
'save': 'sauvegarder',
|
||||
'Save file:': 'Sauvegarder le fichier:',
|
||||
'Save file: %s': 'Sauvegarder le fichier : %s',
|
||||
'Save model as...': 'Sauvegarder le modèle sous...',
|
||||
'Save via Ajax': 'Sauvegarder via Ajax',
|
||||
'Saved file hash:': 'Hash du Fichier enregistré:',
|
||||
'Screenshot %s': 'Screenshot %s',
|
||||
'Search': 'Search',
|
||||
'Screenshot %s': "Capture d'écran %s",
|
||||
'Search': 'Rechercher',
|
||||
'Searching: **%s** %%{file}': 'Searching: **%s** %%{file}',
|
||||
'Select Files to Package': 'Select Files to Package',
|
||||
'selected': 'sélectionnés',
|
||||
@@ -491,34 +492,34 @@
|
||||
'Showing %s to %s of %s %s found': 'Showing %s to %s of %s %s found',
|
||||
'Singular Form': 'Singular Form',
|
||||
'Site': 'Site',
|
||||
'Size of cache:': 'Size of cache:',
|
||||
'Size of cache:': 'Taille de la mémoire cache:',
|
||||
'skip to generate': 'skip to generate',
|
||||
'some files could not be removed': 'certains fichiers ne peuvent pas être supprimés',
|
||||
'Something went wrong please wait a few minutes before retrying': 'Something went wrong please wait a few minutes before retrying',
|
||||
'Sorry, could not find mercurial installed': 'Sorry, could not find mercurial installed',
|
||||
'source : db': 'source : db',
|
||||
'source : filesystem': 'source : filesystem',
|
||||
'Start a new app': 'Start a new app',
|
||||
'Start searching': 'Start searching',
|
||||
'source : filesystem': 'source : système de fichier',
|
||||
'Start a new app': 'Commencer une nouvelle application',
|
||||
'Start searching': 'Débuté la recherche',
|
||||
'Start wizard': "Démarrer l'assistant",
|
||||
'state': 'état',
|
||||
'Static': 'Static',
|
||||
'static': 'statiques',
|
||||
'Static': 'Statique',
|
||||
'static': 'statique',
|
||||
'Static files': 'Fichiers statiques',
|
||||
'Statistics': 'Statistics',
|
||||
'Step': 'Step',
|
||||
'step': 'step',
|
||||
'stop': 'stop',
|
||||
'stop': 'arrêt',
|
||||
'submit': 'envoyer',
|
||||
'Submit': 'Submit',
|
||||
'successful': 'successful',
|
||||
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet? ',
|
||||
'switch to : db': 'switch to : db',
|
||||
'Submit': 'Envoyer',
|
||||
'successful': 'réussi',
|
||||
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet?',
|
||||
'switch to : db': 'transférer dans : db',
|
||||
'switch to : filesystem': 'switch to : filesystem',
|
||||
'Tab width (# characters)': 'Tab width (# characters)',
|
||||
'table': 'table',
|
||||
'Table': 'Table',
|
||||
'Temporary': 'Temporary',
|
||||
'Temporary': 'Temporaire',
|
||||
'test': 'tester',
|
||||
'Testing application': "Test de l'application",
|
||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "requête" est une condition comme "db.table1.field1==\'value\'". Quelque chose comme "db.table1.field1==db.table2.field2" aboutit à un JOIN SQL.',
|
||||
@@ -530,14 +531,14 @@
|
||||
'The data representation, define database tables and sets': 'La représentation des données, définir les tables et ensembles de la base de données',
|
||||
'The presentations layer, views are also known as templates': 'Les couches de présentation, les vues sont également appelées modples',
|
||||
'the presentations layer, views are also known as templates': 'la couche de présentation, les vues sont également appelées modèles',
|
||||
'Theme': 'Theme',
|
||||
'Theme': 'Thème',
|
||||
'There are no controllers': "Il n'y a pas de contrôleurs",
|
||||
'There are no models': "Il n'y a pas de modèles",
|
||||
'There are no modules': "Il n'y a pas de modules",
|
||||
'There are no plugins': "Il n'y a pas de plugins",
|
||||
'There are no private files': 'There are no private files',
|
||||
'There are no private files': "Il n'y a pas de fichiers privés",
|
||||
'There are no static files': "Il n'y a pas de fichiers statiques",
|
||||
'There are no translators': 'There are no translators',
|
||||
'There are no translators': "Il n'y a pas de traducteurs",
|
||||
'There are no translators, only default language is supported': "Il n'y a pas de traducteurs, seule la langue par défaut est prise en charge",
|
||||
'There are no views': "Il n'y a pas de vues",
|
||||
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
|
||||
@@ -552,9 +553,9 @@
|
||||
'this page to see if a breakpoint was hit and debug interaction is required.': 'this page to see if a breakpoint was hit and debug interaction is required.',
|
||||
'This will pull changes from the remote repo for application "%s"?': 'This will pull changes from the remote repo for application "%s"?',
|
||||
'This will push changes to the remote repo for application "%s".': 'This will push changes to the remote repo for application "%s".',
|
||||
'Ticket': 'Ticket',
|
||||
'Ticket ID': 'Ticket ID',
|
||||
'Ticket Missing': 'Ticket Missing',
|
||||
'Ticket': 'Billet',
|
||||
'Ticket ID': 'Identifiant du Billet',
|
||||
'Ticket Missing': 'Billet manquant',
|
||||
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
||||
'TM': 'MD',
|
||||
'to previous version.': 'à la version précédente.',
|
||||
@@ -569,8 +570,8 @@
|
||||
'Translation strings for the application': "Chaînes de traduction pour l'application",
|
||||
'try': 'essayer',
|
||||
'try something like': 'essayez quelque chose comme',
|
||||
'Try the mobile interface': 'Try the mobile interface',
|
||||
'try view': 'try view',
|
||||
'Try the mobile interface': "Essayer l'interface pour mobile",
|
||||
'try view': 'essayer la vue',
|
||||
'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Type PDB debugger command in here and hit Return (Enter) to execute it.',
|
||||
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
|
||||
'Unable to check for upgrades': 'Impossible de vérifier les mises à jour',
|
||||
@@ -595,25 +596,25 @@
|
||||
'update': 'mettre à jour',
|
||||
'update all languages': 'mettre à jour toutes les langues',
|
||||
'Update:': 'Mise à jour:',
|
||||
'Upgrade': 'Upgrade',
|
||||
'Upgrade': 'Mese à jour de version',
|
||||
'upgrade now': 'mettre à jour maintenant',
|
||||
'upgrade now to %s': 'upgrade now to %s',
|
||||
'upgrade now to %s': 'mettre à jour maintenant à %s',
|
||||
'upgrade web2py now': 'mettre à jour web2py maintenant',
|
||||
'upload': 'charger',
|
||||
'Upload': 'Upload',
|
||||
'upload': 'téléversé',
|
||||
'Upload': 'Téléversé',
|
||||
'Upload & install packed application': "Charger & installer l'application empaquetée",
|
||||
'Upload a package:': 'Charger un paquet:',
|
||||
'Upload and install packed application': 'Upload and install packed application',
|
||||
'upload application:': "charger l'application:",
|
||||
'Upload existing application': 'Charger une application existante',
|
||||
'upload file:': 'charger le fichier:',
|
||||
'Upload a package:': 'Téléverser un paquet:',
|
||||
'Upload and install packed application': 'Téléversement et installation du paquet applicatif',
|
||||
'upload application:': "téléverser l'application:",
|
||||
'Upload existing application': 'Téléverser une application existante',
|
||||
'upload file:': 'téléverser le fichier:',
|
||||
'upload plugin file:': 'charger fichier plugin:',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilisez (...)&(...) pour AND, (...)|(...) pour OR, et ~(...) pour NOT afin de construire des requêtes plus complexes. ',
|
||||
'Use an url:': 'Utiliser une url:',
|
||||
'user': 'utilisateur',
|
||||
'User': 'User',
|
||||
'Username': 'Username',
|
||||
'Users': 'Users',
|
||||
'User': 'Utilisateur',
|
||||
'Username': "Nom d'utilisateur",
|
||||
'Users': 'Utilisateurs',
|
||||
'Using the shell may lock the database to other users of this app.': 'Using the shell may lock the database to other users of this app.',
|
||||
'variables': 'variables',
|
||||
'Version': 'Version',
|
||||
@@ -625,9 +626,9 @@
|
||||
'Warning!': 'Warning!',
|
||||
'WARNING:': 'WARNING:',
|
||||
'WARNING: The following views could not be compiled:': 'WARNING: The following views could not be compiled:',
|
||||
'Web Framework': 'Framework Web',
|
||||
'web2py Admin Password': 'web2py Admin Password',
|
||||
'web2py apps to deploy': 'web2py apps to deploy',
|
||||
'Web Framework': 'Cadre Web',
|
||||
'web2py Admin Password': 'Mot de passe admin web2py',
|
||||
'web2py apps to deploy': 'applications web2py à deployer',
|
||||
'web2py Debugger': 'web2py Debugger',
|
||||
'web2py downgrade': 'web2py downgrade',
|
||||
'web2py is up to date': 'web2py est à jour',
|
||||
@@ -635,10 +636,10 @@
|
||||
'web2py Recent Tweets': 'Tweets récents sur web2py ',
|
||||
'web2py upgrade': 'web2py upgrade',
|
||||
'web2py upgraded; please restart it': 'web2py mis à jour; veuillez le redémarrer',
|
||||
'Working...': 'Working...',
|
||||
'Working...': 'Travail en cours...',
|
||||
'WSGI reference name': 'WSGI reference name',
|
||||
'YES': 'OUI',
|
||||
'Yes': 'Yes',
|
||||
'Yes': 'Oui',
|
||||
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button',
|
||||
'You can inspect variables using the console below': 'You can inspect variables using the console below',
|
||||
'You have one more login attempt before you are locked out': 'You have one more login attempt before you are locked out',
|
||||
|
||||
@@ -52,7 +52,7 @@ def verify_password(password):
|
||||
if DEMO_MODE:
|
||||
ret = True
|
||||
elif not _config.get('password'):
|
||||
ret - False
|
||||
ret = False
|
||||
elif _config['password'].startswith('pam_user:'):
|
||||
session.pam_user = _config['password'][9:].strip()
|
||||
import gluon.contrib.pam
|
||||
|
||||
33
applications/admin/static/css/d3_graph.css
Normal file
33
applications/admin/static/css/d3_graph.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.node {fill: steelblue;
|
||||
stroke: #636363;
|
||||
stroke-width: 1px;}
|
||||
|
||||
.auth {fill: lightgrey;}
|
||||
|
||||
.table {r: 10;}
|
||||
|
||||
.link {stroke: #bbbbbb;
|
||||
stroke-width: 2px;}
|
||||
td {padding: 4px;}
|
||||
|
||||
div.tooltip {
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
/* width: 140px; */
|
||||
/* height: 28px;*/
|
||||
padding: 0px 5px 0px 5px;
|
||||
padding-top: 0px;
|
||||
font: 12px sans-serif;
|
||||
background: #fff7bc;
|
||||
border: solid 1px #aaa;
|
||||
border-radius: 6px;
|
||||
pointer-events: none;}
|
||||
|
||||
h5 { font: 14px sans-serif;
|
||||
background : #ec7014;
|
||||
color: #ffffe5;
|
||||
padding: 5px 2px 5px 2px;
|
||||
margin-top: 1px;}
|
||||
path {
|
||||
fill: #aaaaaa;}
|
||||
|
||||
8
applications/admin/static/js/d3.min.js
vendored
Normal file
8
applications/admin/static/js/d3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
181
applications/admin/static/js/d3_graph.js
vendored
Normal file
181
applications/admin/static/js/d3_graph.js
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
function d3_graph() {
|
||||
|
||||
// Some reference links:
|
||||
// How to get link ids instead of index
|
||||
// http://stackoverflow.com/questions/23986466/d3-force-layout-linking-nodes-by-name-instead-of-index
|
||||
// embedding web2py in d3
|
||||
// http://stackoverflow.com/questions/34326343/embedding-d3-js-graph-in-a-web2py-bootstrap-page
|
||||
|
||||
// nodes and links are defined in appadmin.html <script>
|
||||
|
||||
|
||||
var edges = [];
|
||||
|
||||
links.forEach(function(e) {
|
||||
var sourceNode = nodes.filter(function(n) {
|
||||
return n.name === e.source;
|
||||
})[0],
|
||||
targetNode = nodes.filter(function(n) {
|
||||
return n.name === e.target;
|
||||
})[0];
|
||||
|
||||
edges.push({
|
||||
source: sourceNode,
|
||||
target: targetNode,
|
||||
value: 1});
|
||||
|
||||
});
|
||||
|
||||
edges.forEach(function(e) {
|
||||
|
||||
if (!e.source["linkcount"]) e.source["linkcount"] = 0;
|
||||
if (!e.target["linkcount"]) e.target["linkcount"] = 0;
|
||||
|
||||
e.source["linkcount"]++;
|
||||
e.target["linkcount"]++;
|
||||
});
|
||||
|
||||
//var width = 960, height = 600;
|
||||
var height = window.innerHeight|| docEl.clientHeight|| bodyEl.clientHeight;
|
||||
var width = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth;
|
||||
var svg = d3.select("#vis").append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
// updated for d3 v4.
|
||||
var simulation = d3.forceSimulation()
|
||||
.force("link", d3.forceLink().id(function(d) { return d.id; }))
|
||||
.force("charge", d3.forceManyBody().strength(strength))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
.force("collision", d3.forceCollide(35));
|
||||
|
||||
// Node charge strength. Repel strength greater for less links.
|
||||
//function strength(d) { return -50/d["linkcount"] ; }
|
||||
function strength(d) { return -25 ; }
|
||||
|
||||
// Link distance. Distance increases with number of links at source and target
|
||||
function distance(d) { return (60 + (d.source["linkcount"] * d.target["linkcount"])) ; }
|
||||
|
||||
// Link strength. Strength is less for highly connected nodes (move towards target dist)
|
||||
function strengthl(d) { return 5/(d.source["linkcount"] + d.target["linkcount"]) ; }
|
||||
|
||||
simulation
|
||||
.nodes(nodes)
|
||||
.on("tick", tick);
|
||||
|
||||
simulation.force("link")
|
||||
.links(edges)
|
||||
.distance(distance)
|
||||
.strength(strengthl);
|
||||
|
||||
// build the arrow.
|
||||
svg.append("svg:defs").selectAll("marker")
|
||||
.data(["end"]) // Different link/path types can be defined here
|
||||
.enter().append("svg:marker") // This section adds in the arrows
|
||||
.attr("id", String)
|
||||
.attr("viewBox", "0 -5 10 10")
|
||||
.attr("refX", 25) // Moves the arrow head out, allow for radius
|
||||
.attr("refY", 0) // -1.5
|
||||
.attr("markerWidth", 6)
|
||||
.attr("markerHeight", 6)
|
||||
.attr("orient", "auto")
|
||||
.append("svg:path")
|
||||
.attr("d", "M0,-5L10,0L0,5");
|
||||
|
||||
var link = svg.selectAll('.link')
|
||||
.data(edges)
|
||||
.enter().append('line')
|
||||
.attr("class", "link")
|
||||
.attr("marker-end", "url(#end)");
|
||||
|
||||
var node = svg.selectAll(".node")
|
||||
.data(nodes)
|
||||
.enter().append("g")
|
||||
.attr("class", function(d) { return "node " + d.type;})
|
||||
.attr('transform', function(d) {
|
||||
return "translate(" + d.x + "," + d.y + ")"})
|
||||
.classed("auth", function(d) { return (d.name.startsWith("auth") ? true : false);});
|
||||
|
||||
node.call(d3.drag()
|
||||
.on("start", dragstarted)
|
||||
.on("drag", dragged)
|
||||
.on("end", dragended));
|
||||
|
||||
// add the nodes
|
||||
node.append('circle')
|
||||
.attr('r', 16)
|
||||
;
|
||||
|
||||
// add text
|
||||
node.append("text")
|
||||
.attr("x", 12)
|
||||
.attr("dy", "-1.1em")
|
||||
.text(function(d) {return d.name;});
|
||||
|
||||
node.on("mouseover", function(d) {
|
||||
|
||||
var g = d3.select(this); // the node (table)
|
||||
|
||||
// tooltip
|
||||
|
||||
var fields = d.fields;
|
||||
var fieldformat = "<TABLE>";
|
||||
fields.forEach(function(d) {
|
||||
fieldformat += "<TR><TD><B>"+ d.name+"</B></TD><TD>"+ d.type+"</TD><TD>"+ d.disp+"</TD></TR>";
|
||||
});
|
||||
fieldformat += "</TABLE>";
|
||||
var tiplength = d.fields.length;
|
||||
|
||||
// Define 'div' for tooltips
|
||||
var div = d3.select("body").append("div") // declare the tooltip div
|
||||
.attr("class", "tooltip") // apply the 'tooltip' class
|
||||
.style("opacity", 0)
|
||||
.html('<h5>' + d.name + '</h5>' + fieldformat)
|
||||
.style("left", 20 + (d3.event.pageX) + "px")// or just (d.x + 50 + "px")
|
||||
.style("top", tooltop(tiplength))// or ...
|
||||
.transition()
|
||||
.duration(800)
|
||||
.style("opacity", 0.9);
|
||||
});
|
||||
|
||||
function tooltop(tiplength) {
|
||||
//aim to ensure tooltip is fully visible whenver possible
|
||||
return (Math.max(d3.event.pageY - 20 - (tiplength * 14),0)) + "px"
|
||||
}
|
||||
|
||||
node.on("mouseout", function(d) {
|
||||
d3.select("body").select('div.tooltip').remove();
|
||||
});
|
||||
|
||||
// instead of waiting for force to end with : force.on('end', function()
|
||||
// use .on("tick", instead. Here is the tick function
|
||||
function tick() {
|
||||
node.attr('transform', function(d) {
|
||||
d.x = Math.max(30, Math.min(width - 16, d.x));
|
||||
d.y = Math.max(30, Math.min(height - 16, d.y));
|
||||
return "translate(" + d.x + "," + d.y + ")"; });
|
||||
|
||||
link.attr('x1', function(d) {return d.source.x;})
|
||||
.attr('y1', function(d) {return d.source.y;})
|
||||
.attr('x2', function(d) {return d.target.x;})
|
||||
.attr('y2', function(d) {return d.target.y;});
|
||||
};
|
||||
|
||||
function dragstarted(d) {
|
||||
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
};
|
||||
|
||||
function dragged(d) {
|
||||
d.fx = d3.event.x;
|
||||
d.fy = d3.event.y;
|
||||
};
|
||||
|
||||
function dragended(d) {
|
||||
if (!d3.event.active) simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
d.fy = null;
|
||||
};
|
||||
|
||||
};
|
||||
9
applications/admin/static/js/jquery.js
vendored
9
applications/admin/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -12,6 +12,10 @@
|
||||
$.error('web2py.js has already been loaded!');
|
||||
}
|
||||
|
||||
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||
var animateIn = 'fadeIn';
|
||||
// var animateIn = 'slideDown';
|
||||
|
||||
String.prototype.reverse = function () {
|
||||
return this.split('').reverse().join('');
|
||||
};
|
||||
@@ -38,8 +42,12 @@
|
||||
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
||||
else $('#' + id).show().fadeOut('slow');
|
||||
},
|
||||
ajax: function (u, s, t) {
|
||||
ajax: function (u, s, t, options) {
|
||||
/*simple ajax function*/
|
||||
|
||||
// set options default value
|
||||
options = typeof options !== 'undefined' ? options : {};
|
||||
|
||||
var query = '';
|
||||
if (typeof s == 'string') {
|
||||
var d = $(s).serialize();
|
||||
@@ -59,18 +67,44 @@
|
||||
query = pcs.join('&');
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
|
||||
// default success action
|
||||
var success_function = function (msg) {
|
||||
if (t) {
|
||||
if (t == ':eval') eval(msg);
|
||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
||||
else t(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// declare success actions as array
|
||||
var success = [success_function];
|
||||
|
||||
// add user success actions
|
||||
if ($.isArray(options.done)){
|
||||
success = $.merge(success, options.done);
|
||||
} else {
|
||||
success.push(options.done);
|
||||
}
|
||||
|
||||
// default jquery ajax options
|
||||
var ajax_options = {
|
||||
type: 'POST',
|
||||
url: u,
|
||||
data: query,
|
||||
success: function (msg) {
|
||||
if (t) {
|
||||
if (t == ':eval') eval(msg);
|
||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
||||
else t(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
success: success
|
||||
};
|
||||
|
||||
//remove custom "done" option if exists
|
||||
delete options.done;
|
||||
|
||||
// merge default ajax options with user custom options
|
||||
for (var attrname in options) {
|
||||
ajax_options[attrname] = options[attrname];
|
||||
}
|
||||
|
||||
// call ajax function
|
||||
$.ajax(ajax_options);
|
||||
},
|
||||
ajax_fields: function (target) {
|
||||
/*
|
||||
@@ -148,7 +182,7 @@
|
||||
},
|
||||
/* manage errors in forms */
|
||||
manage_errors: function (target) {
|
||||
$('div.error', target).hide().slideDown('slow');
|
||||
$('div.error', target).hide()[animateIn]('slow');
|
||||
},
|
||||
after_ajax: function (xhr) {
|
||||
/* called whenever an ajax request completes */
|
||||
@@ -233,13 +267,17 @@
|
||||
}
|
||||
});
|
||||
/* help preventing double form submission for normal form (not LOADed) */
|
||||
$(doc).on('submit', 'form', function () {
|
||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
||||
web2py.disableElement(submit_button);
|
||||
$(doc).on('submit', 'form', function (e) {
|
||||
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||
submit_buttons.each(function() {
|
||||
web2py.disableElement($(this));
|
||||
})
|
||||
/* safeguard in case the form doesn't trigger a refresh,
|
||||
see https://github.com/web2py/web2py/issues/1100 */
|
||||
setTimeout(function () {
|
||||
web2py.enableElement(submit_button);
|
||||
submit_buttons.each(function() {
|
||||
web2py.enableElement($(this));
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
doc.ajaxSuccess(function (e, xhr) {
|
||||
@@ -290,7 +328,15 @@
|
||||
form.submit(function (e) {
|
||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||
web2py.hide_flash();
|
||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
||||
|
||||
var formData;
|
||||
if (FORMDATA_IS_SUPPORTED) {
|
||||
formData = new FormData(form[0]); // Allows file uploads.
|
||||
} else {
|
||||
formData = form.serialize(); // Fallback for older browsers.
|
||||
}
|
||||
web2py.ajax_page('post', url, formData, target, form);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||
@@ -309,11 +355,18 @@
|
||||
if (web2py.isUndefined(element)) element = $(document);
|
||||
/* if target is not there, fill it with something that there isn't in the page*/
|
||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||
|
||||
/* processData and contentType must be set to false when passing a FormData
|
||||
object to jQuery.ajax. */
|
||||
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||
$.ajax({
|
||||
'type': method,
|
||||
'url': action,
|
||||
'data': data,
|
||||
'processData': !isFormData,
|
||||
'contentType': contentType,
|
||||
'beforeSend': function (xhr, settings) {
|
||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||
xhr.setRequestHeader('web2py-component-element', target);
|
||||
@@ -564,8 +617,8 @@
|
||||
flash: function (message, status) {
|
||||
var flash = $('.w2p_flash');
|
||||
web2py.hide_flash();
|
||||
flash.html(message).addClass(status);
|
||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
||||
flash.text(message).addClass(status);
|
||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>')[animateIn]();
|
||||
},
|
||||
hide_flash: function () {
|
||||
$('.w2p_flash').fadeOut(0).html('');
|
||||
@@ -579,7 +632,7 @@
|
||||
for (var k = 0; k < triggers[id].length; k++) {
|
||||
var dep = $('#' + triggers[id][k], target);
|
||||
var tr = $('#' + triggers[id][k] + '__row', target);
|
||||
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
|
||||
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
|
||||
else tr.hide();
|
||||
}
|
||||
};
|
||||
@@ -669,8 +722,9 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Does not disable elements with 'data-w2p_disable' attribute
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
disableFormElements: function (form) {
|
||||
@@ -682,13 +736,15 @@
|
||||
if (!web2py.isUndefined(disable)) {
|
||||
return false;
|
||||
}
|
||||
if (web2py.isUndefined(disable_with)) {
|
||||
element.data('w2p_disable_with', element[method]());
|
||||
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||
if (web2py.isUndefined(disable_with)) {
|
||||
element.data('w2p_disable_with', element[method]());
|
||||
}
|
||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
}
|
||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
});
|
||||
},
|
||||
@@ -769,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -233,30 +233,22 @@
|
||||
<div class="clear"></div>
|
||||
{{pass}}
|
||||
|
||||
{{if request.function=='graph_model':}}
|
||||
{{if request.function=='d3_graph_model':}}
|
||||
<h2>{{=T("Graph Model")}}</h2>
|
||||
{{if not pgv:}}
|
||||
{{=T('pygraphviz library not found')}}
|
||||
{{elif not databases:}}
|
||||
{{if not databases:}}
|
||||
{{=T("No databases in this application")}}
|
||||
{{else:}}
|
||||
<div class="btn-group">
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="icon-download"></i> {{=T('Save model as...')}}
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
|
||||
{{else:}}
|
||||
<div id="vis"></div>
|
||||
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||
<script>
|
||||
// Define the d3 input data
|
||||
{{from gluon.serializers import json }}
|
||||
var nodes = {{=XML(json(nodes))}};
|
||||
var links = {{=XML(json(links))}};
|
||||
d3_graph();
|
||||
</script>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
|
||||
{{if request.function == 'manage':}}
|
||||
<h2>{{=heading}}</h2>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.3.1.min.css')}}">
|
||||
|
||||
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
|
||||
|
||||
{{include 'web2py_ajax.html'}}
|
||||
|
||||
|
||||
@@ -105,7 +105,9 @@ def deletefile(arglist, vars={}):
|
||||
{{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}}
|
||||
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
|
||||
{{pass}}
|
||||
{{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}}
|
||||
{{if os.access(os.path.join(request.folder,'..','admin','static','js','d3_graph.js'),os.R_OK):}}
|
||||
{{=button(URL(a=app, c='appadmin',f='d3_graph_model'), T('graph model'))}}
|
||||
{{pass}}
|
||||
</div>
|
||||
<ul class="unstyled act_edit">
|
||||
{{for m in models:}}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
{{=T('Manage')}}
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
|
||||
@@ -7,7 +7,7 @@ It is used as default when a view is not provided for your controllers
|
||||
"""}}
|
||||
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
|
||||
{{if len(response._vars)==1:}}
|
||||
{{=BEAUTIFY(response._vars.values()[0])}}
|
||||
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
|
||||
{{elif len(response._vars)>1:}}
|
||||
{{=BEAUTIFY(response._vars)}}
|
||||
{{pass}}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.3.1.min.css')}}">
|
||||
|
||||
<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script!>
|
||||
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
|
||||
|
||||
{{include 'web2py_ajax.html'}}
|
||||
|
||||
|
||||
@@ -12,11 +12,6 @@ import gluon.contenttype
|
||||
import gluon.fileutils
|
||||
from gluon._compat import iteritems
|
||||
|
||||
try:
|
||||
import pygraphviz as pgv
|
||||
except ImportError:
|
||||
pgv = None
|
||||
|
||||
is_gae = request.env.web2py_runtime_gae or False
|
||||
|
||||
# ## critical --- make a copy of the environment
|
||||
@@ -565,57 +560,6 @@ def table_template(table):
|
||||
_cellborder=0, _cellspacing=0)
|
||||
).xml()
|
||||
|
||||
|
||||
def bg_graph_model():
|
||||
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
|
||||
|
||||
subgraphs = dict()
|
||||
for tablename in db.tables:
|
||||
if hasattr(db[tablename],'_meta_graphmodel'):
|
||||
meta_graphmodel = db[tablename]._meta_graphmodel
|
||||
else:
|
||||
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
||||
|
||||
group = meta_graphmodel['group'].replace(' ', '')
|
||||
if group not in subgraphs:
|
||||
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
||||
subgraphs[group]['tables'].append(tablename)
|
||||
|
||||
graph.add_node(tablename, name=tablename, shape='plaintext',
|
||||
label=table_template(tablename))
|
||||
|
||||
for n, key in enumerate(subgraphs.iterkeys()):
|
||||
graph.subgraph(nbunch=subgraphs[key]['tables'],
|
||||
name='cluster%d' % n,
|
||||
style='filled',
|
||||
color=subgraphs[key]['meta']['color'],
|
||||
label=subgraphs[key]['meta']['group'])
|
||||
|
||||
for tablename in db.tables:
|
||||
for field in db[tablename]:
|
||||
f_type = field.type
|
||||
if isinstance(f_type,str) and (
|
||||
f_type.startswith('reference') or
|
||||
f_type.startswith('list:reference')):
|
||||
referenced_table = f_type.split()[1].split('.')[0]
|
||||
n1 = graph.get_node(tablename)
|
||||
n2 = graph.get_node(referenced_table)
|
||||
graph.add_edge(n1, n2, color="#4C4C4C", label='')
|
||||
|
||||
graph.layout()
|
||||
if not request.args:
|
||||
response.headers['Content-Type'] = 'image/png'
|
||||
return graph.draw(format='png', prog='dot')
|
||||
else:
|
||||
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
|
||||
if request.args(0) == 'dot':
|
||||
return graph.string()
|
||||
else:
|
||||
return graph.draw(format=request.args(0), prog='dot')
|
||||
|
||||
def graph_model():
|
||||
return dict(databases=databases, pgv=pgv)
|
||||
|
||||
def manage():
|
||||
tables = manager_action['tables']
|
||||
if isinstance(tables[0], str):
|
||||
@@ -700,3 +644,51 @@ def hooks():
|
||||
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
|
||||
ul_main.append(ul_t)
|
||||
return ul_main
|
||||
|
||||
|
||||
# ##########################################################
|
||||
# d3 based model visualizations
|
||||
# ###########################################################
|
||||
|
||||
def d3_graph_model():
|
||||
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
|
||||
and also the app_admin bg_graph_model function
|
||||
|
||||
Create a list of table dicts, called "nodes"
|
||||
"""
|
||||
|
||||
nodes = []
|
||||
links = []
|
||||
|
||||
for database in databases:
|
||||
db = eval_in_global_env(database)
|
||||
for tablename in db.tables:
|
||||
fields = []
|
||||
for field in db[tablename]:
|
||||
f_type = field.type
|
||||
if not isinstance(f_type,str):
|
||||
disp = ' '
|
||||
elif f_type == 'string':
|
||||
disp = field.length
|
||||
elif f_type == 'id':
|
||||
disp = "PK"
|
||||
elif f_type.startswith('reference') or \
|
||||
f_type.startswith('list:reference'):
|
||||
disp = "FK"
|
||||
else:
|
||||
disp = ' '
|
||||
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
||||
|
||||
if isinstance(f_type,str) and (
|
||||
f_type.startswith('reference') or
|
||||
f_type.startswith('list:reference')):
|
||||
referenced_table = f_type.split()[1].split('.')[0]
|
||||
|
||||
links.append(dict(source=tablename, target = referenced_table))
|
||||
|
||||
nodes.append(dict(name=tablename, type="table", fields = fields))
|
||||
|
||||
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
||||
response.files.append(URL('admin','static','js/d3.min.js'))
|
||||
response.files.append(URL('admin','static','js/d3_graph.js'))
|
||||
return dict(databases=databases, nodes=nodes, links=links)
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
- **Created by a community of professionals** and University professors in Computer Science and Software Engineering.
|
||||
- **Always backward compatible.** We have not broken backward compatibility since version 1.0 in 2007, and we pledge not to break it in the future.
|
||||
- **Easy to run.** It requires no installation and no configuration.
|
||||
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.5/2.6/2.7/pypy, or Java with Jython.
|
||||
- **Runs with** Apache, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
|
||||
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Oracle, IBM DB2, Informix, Ingres, and Google App Engine.
|
||||
- **Runs on** Windows, Mac, Unix/Linux, Google App Engine, Amazon EC2, and almost any web hosting via Python 2.7/3.5/3.6/pypy.
|
||||
- **Runs with** Apache, Nginx, Lighttpd, Cherokee and almost any other web server via CGI, FastCGI, WSGI, mod_proxy, and/or mod_python. It can embed third party WSGI apps and middleware.
|
||||
- **Talks to** SQLite, PostgreSQL, MySQL, MSSQL, FireBird, Sybase, Oracle, IBM DB2, Informix, Ingres, MongoDB, and Google App Engine.
|
||||
- **Secure** [[It prevents the most common types of vulnerabilities http://web2py.com/examples/default/security]] including Cross Site Scripting, Injection Flaws, and Malicious File Execution.
|
||||
- **Enforces good Software Engineering practices** (Model-View-Controller design, Server-side form validation, postbacks) that make the code more readable, scalable, and maintainable.
|
||||
- **Speaks multiple protocols** HTML/XML, RSS/ATOM, RTF, PDF, JSON, AJAX, XML-RPC, CSV, REST, WIKI, Flash/AMF, and Linked Data (RDF).
|
||||
|
||||
9
applications/examples/static/js/jquery.js
vendored
9
applications/examples/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -12,6 +12,10 @@
|
||||
$.error('web2py.js has already been loaded!');
|
||||
}
|
||||
|
||||
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||
var animateIn = 'fadeIn';
|
||||
// var animateIn = 'slideDown';
|
||||
|
||||
String.prototype.reverse = function () {
|
||||
return this.split('').reverse().join('');
|
||||
};
|
||||
@@ -38,8 +42,12 @@
|
||||
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
||||
else $('#' + id).show().fadeOut('slow');
|
||||
},
|
||||
ajax: function (u, s, t) {
|
||||
ajax: function (u, s, t, options) {
|
||||
/*simple ajax function*/
|
||||
|
||||
// set options default value
|
||||
options = typeof options !== 'undefined' ? options : {};
|
||||
|
||||
var query = '';
|
||||
if (typeof s == 'string') {
|
||||
var d = $(s).serialize();
|
||||
@@ -59,18 +67,44 @@
|
||||
query = pcs.join('&');
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
|
||||
// default success action
|
||||
var success_function = function (msg) {
|
||||
if (t) {
|
||||
if (t == ':eval') eval(msg);
|
||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
||||
else t(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// declare success actions as array
|
||||
var success = [success_function];
|
||||
|
||||
// add user success actions
|
||||
if ($.isArray(options.done)){
|
||||
success = $.merge(success, options.done);
|
||||
} else {
|
||||
success.push(options.done);
|
||||
}
|
||||
|
||||
// default jquery ajax options
|
||||
var ajax_options = {
|
||||
type: 'POST',
|
||||
url: u,
|
||||
data: query,
|
||||
success: function (msg) {
|
||||
if (t) {
|
||||
if (t == ':eval') eval(msg);
|
||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
||||
else t(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
success: success
|
||||
};
|
||||
|
||||
//remove custom "done" option if exists
|
||||
delete options.done;
|
||||
|
||||
// merge default ajax options with user custom options
|
||||
for (var attrname in options) {
|
||||
ajax_options[attrname] = options[attrname];
|
||||
}
|
||||
|
||||
// call ajax function
|
||||
$.ajax(ajax_options);
|
||||
},
|
||||
ajax_fields: function (target) {
|
||||
/*
|
||||
@@ -148,7 +182,7 @@
|
||||
},
|
||||
/* manage errors in forms */
|
||||
manage_errors: function (target) {
|
||||
$('div.error', target).hide().slideDown('slow');
|
||||
$('div.error', target).hide()[animateIn]('slow');
|
||||
},
|
||||
after_ajax: function (xhr) {
|
||||
/* called whenever an ajax request completes */
|
||||
@@ -233,13 +267,17 @@
|
||||
}
|
||||
});
|
||||
/* help preventing double form submission for normal form (not LOADed) */
|
||||
$(doc).on('submit', 'form', function () {
|
||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
||||
web2py.disableElement(submit_button);
|
||||
$(doc).on('submit', 'form', function (e) {
|
||||
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||
submit_buttons.each(function() {
|
||||
web2py.disableElement($(this));
|
||||
})
|
||||
/* safeguard in case the form doesn't trigger a refresh,
|
||||
see https://github.com/web2py/web2py/issues/1100 */
|
||||
setTimeout(function () {
|
||||
web2py.enableElement(submit_button);
|
||||
submit_buttons.each(function() {
|
||||
web2py.enableElement($(this));
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
doc.ajaxSuccess(function (e, xhr) {
|
||||
@@ -290,7 +328,15 @@
|
||||
form.submit(function (e) {
|
||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||
web2py.hide_flash();
|
||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
||||
|
||||
var formData;
|
||||
if (FORMDATA_IS_SUPPORTED) {
|
||||
formData = new FormData(form[0]); // Allows file uploads.
|
||||
} else {
|
||||
formData = form.serialize(); // Fallback for older browsers.
|
||||
}
|
||||
web2py.ajax_page('post', url, formData, target, form);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||
@@ -309,11 +355,18 @@
|
||||
if (web2py.isUndefined(element)) element = $(document);
|
||||
/* if target is not there, fill it with something that there isn't in the page*/
|
||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||
|
||||
/* processData and contentType must be set to false when passing a FormData
|
||||
object to jQuery.ajax. */
|
||||
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||
$.ajax({
|
||||
'type': method,
|
||||
'url': action,
|
||||
'data': data,
|
||||
'processData': !isFormData,
|
||||
'contentType': contentType,
|
||||
'beforeSend': function (xhr, settings) {
|
||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||
xhr.setRequestHeader('web2py-component-element', target);
|
||||
@@ -564,8 +617,8 @@
|
||||
flash: function (message, status) {
|
||||
var flash = $('.w2p_flash');
|
||||
web2py.hide_flash();
|
||||
flash.html(message).addClass(status);
|
||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
||||
flash.text(message).addClass(status);
|
||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>')[animateIn]();
|
||||
},
|
||||
hide_flash: function () {
|
||||
$('.w2p_flash').fadeOut(0).html('');
|
||||
@@ -579,7 +632,7 @@
|
||||
for (var k = 0; k < triggers[id].length; k++) {
|
||||
var dep = $('#' + triggers[id][k], target);
|
||||
var tr = $('#' + triggers[id][k] + '__row', target);
|
||||
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
|
||||
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
|
||||
else tr.hide();
|
||||
}
|
||||
};
|
||||
@@ -669,8 +722,9 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Does not disable elements with 'data-w2p_disable' attribute
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
disableFormElements: function (form) {
|
||||
@@ -682,13 +736,15 @@
|
||||
if (!web2py.isUndefined(disable)) {
|
||||
return false;
|
||||
}
|
||||
if (web2py.isUndefined(disable_with)) {
|
||||
element.data('w2p_disable_with', element[method]());
|
||||
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||
if (web2py.isUndefined(disable_with)) {
|
||||
element.data('w2p_disable_with', element[method]());
|
||||
}
|
||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
}
|
||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
});
|
||||
},
|
||||
@@ -769,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -233,30 +233,22 @@
|
||||
<div class="clear"></div>
|
||||
{{pass}}
|
||||
|
||||
{{if request.function=='graph_model':}}
|
||||
{{if request.function=='d3_graph_model':}}
|
||||
<h2>{{=T("Graph Model")}}</h2>
|
||||
{{if not pgv:}}
|
||||
{{=T('pygraphviz library not found')}}
|
||||
{{elif not databases:}}
|
||||
{{if not databases:}}
|
||||
{{=T("No databases in this application")}}
|
||||
{{else:}}
|
||||
<div class="btn-group">
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="icon-download"></i> {{=T('Save model as...')}}
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
|
||||
{{else:}}
|
||||
<div id="vis"></div>
|
||||
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||
<script>
|
||||
// Define the d3 input data
|
||||
{{from gluon.serializers import json }}
|
||||
var nodes = {{=XML(json(nodes))}};
|
||||
var links = {{=XML(json(links))}};
|
||||
d3_graph();
|
||||
</script>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
|
||||
{{if request.function == 'manage':}}
|
||||
<h2>{{=heading}}</h2>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a class="btn btn180 rounded green" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
|
||||
<a class="btn btn180 rounded green" href="http://mdipierro.github.io/web2py/web2py_manual_5th.pdf">Manual</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
|
||||
|
||||
@@ -7,7 +7,7 @@ It is used as default when a view is not provided for your controllers
|
||||
"""}}
|
||||
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
|
||||
{{if len(response._vars)==1:}}
|
||||
{{=BEAUTIFY(response._vars.values()[0])}}
|
||||
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
|
||||
{{elif len(response._vars)>1:}}
|
||||
{{=BEAUTIFY(response._vars)}}
|
||||
{{pass}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{response.headers['web2py-response-flash']=response.flash}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
|
||||
{{response.headers['web2py-response-flash']=response.flash}}{{if len(response._vars)==1:}}{{=response._vars[next(iter(response._vars))]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
|
||||
@@ -12,11 +12,6 @@ import gluon.contenttype
|
||||
import gluon.fileutils
|
||||
from gluon._compat import iteritems
|
||||
|
||||
try:
|
||||
import pygraphviz as pgv
|
||||
except ImportError:
|
||||
pgv = None
|
||||
|
||||
is_gae = request.env.web2py_runtime_gae or False
|
||||
|
||||
# ## critical --- make a copy of the environment
|
||||
@@ -397,12 +392,11 @@ def ccache():
|
||||
cache.disk.clear()
|
||||
session.flash += T("Disk Cleared")
|
||||
redirect(URL(r=request))
|
||||
|
||||
|
||||
try:
|
||||
from guppy import hpy
|
||||
hp = hpy()
|
||||
from pympler.asizeof import asizeof
|
||||
except ImportError:
|
||||
hp = False
|
||||
asizeof = False
|
||||
|
||||
import shelve
|
||||
import os
|
||||
@@ -456,9 +450,9 @@ def ccache():
|
||||
ram['ratio'] = 0
|
||||
|
||||
for key, value in iteritems(cache.ram.storage):
|
||||
if hp:
|
||||
ram['bytes'] += hp.iso(value[1]).size
|
||||
ram['objects'] += hp.iso(value[1]).count
|
||||
if asizeof:
|
||||
ram['bytes'] += asizeof(value[1])
|
||||
ram['objects'] += 1
|
||||
ram['entries'] += 1
|
||||
if value[0] < ram['oldest']:
|
||||
ram['oldest'] = value[0]
|
||||
@@ -474,9 +468,9 @@ def ccache():
|
||||
except (KeyError, ZeroDivisionError):
|
||||
disk['ratio'] = 0
|
||||
else:
|
||||
if hp:
|
||||
disk['bytes'] += hp.iso(value[1]).size
|
||||
disk['objects'] += hp.iso(value[1]).count
|
||||
if asizeof:
|
||||
disk['bytes'] += asizeof(value[1])
|
||||
disk['objects'] += 1
|
||||
disk['entries'] += 1
|
||||
if value[0] < disk['oldest']:
|
||||
disk['oldest'] = value[0]
|
||||
@@ -516,7 +510,7 @@ def ccache():
|
||||
total['keys'] = key_table(total['keys'])
|
||||
|
||||
return dict(form=form, total=total,
|
||||
ram=ram, disk=disk, object_stats=hp != False)
|
||||
ram=ram, disk=disk, object_stats=asizeof != False)
|
||||
|
||||
|
||||
def table_template(table):
|
||||
@@ -565,57 +559,6 @@ def table_template(table):
|
||||
_cellborder=0, _cellspacing=0)
|
||||
).xml()
|
||||
|
||||
|
||||
def bg_graph_model():
|
||||
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
|
||||
|
||||
subgraphs = dict()
|
||||
for tablename in db.tables:
|
||||
if hasattr(db[tablename],'_meta_graphmodel'):
|
||||
meta_graphmodel = db[tablename]._meta_graphmodel
|
||||
else:
|
||||
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
||||
|
||||
group = meta_graphmodel['group'].replace(' ', '')
|
||||
if group not in subgraphs:
|
||||
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
||||
subgraphs[group]['tables'].append(tablename)
|
||||
|
||||
graph.add_node(tablename, name=tablename, shape='plaintext',
|
||||
label=table_template(tablename))
|
||||
|
||||
for n, key in enumerate(subgraphs.iterkeys()):
|
||||
graph.subgraph(nbunch=subgraphs[key]['tables'],
|
||||
name='cluster%d' % n,
|
||||
style='filled',
|
||||
color=subgraphs[key]['meta']['color'],
|
||||
label=subgraphs[key]['meta']['group'])
|
||||
|
||||
for tablename in db.tables:
|
||||
for field in db[tablename]:
|
||||
f_type = field.type
|
||||
if isinstance(f_type,str) and (
|
||||
f_type.startswith('reference') or
|
||||
f_type.startswith('list:reference')):
|
||||
referenced_table = f_type.split()[1].split('.')[0]
|
||||
n1 = graph.get_node(tablename)
|
||||
n2 = graph.get_node(referenced_table)
|
||||
graph.add_edge(n1, n2, color="#4C4C4C", label='')
|
||||
|
||||
graph.layout()
|
||||
if not request.args:
|
||||
response.headers['Content-Type'] = 'image/png'
|
||||
return graph.draw(format='png', prog='dot')
|
||||
else:
|
||||
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
|
||||
if request.args(0) == 'dot':
|
||||
return graph.string()
|
||||
else:
|
||||
return graph.draw(format=request.args(0), prog='dot')
|
||||
|
||||
def graph_model():
|
||||
return dict(databases=databases, pgv=pgv)
|
||||
|
||||
def manage():
|
||||
tables = manager_action['tables']
|
||||
if isinstance(tables[0], str):
|
||||
@@ -700,3 +643,51 @@ def hooks():
|
||||
ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']]))
|
||||
ul_main.append(ul_t)
|
||||
return ul_main
|
||||
|
||||
|
||||
# ##########################################################
|
||||
# d3 based model visualizations
|
||||
# ###########################################################
|
||||
|
||||
def d3_graph_model():
|
||||
""" See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha
|
||||
and also the app_admin bg_graph_model function
|
||||
|
||||
Create a list of table dicts, called "nodes"
|
||||
"""
|
||||
|
||||
nodes = []
|
||||
links = []
|
||||
|
||||
for database in databases:
|
||||
db = eval_in_global_env(database)
|
||||
for tablename in db.tables:
|
||||
fields = []
|
||||
for field in db[tablename]:
|
||||
f_type = field.type
|
||||
if not isinstance(f_type,str):
|
||||
disp = ' '
|
||||
elif f_type == 'string':
|
||||
disp = field.length
|
||||
elif f_type == 'id':
|
||||
disp = "PK"
|
||||
elif f_type.startswith('reference') or \
|
||||
f_type.startswith('list:reference'):
|
||||
disp = "FK"
|
||||
else:
|
||||
disp = ' '
|
||||
fields.append(dict(name= field.name, type=field.type, disp = disp))
|
||||
|
||||
if isinstance(f_type,str) and (
|
||||
f_type.startswith('reference') or
|
||||
f_type.startswith('list:reference')):
|
||||
referenced_table = f_type.split()[1].split('.')[0]
|
||||
|
||||
links.append(dict(source=tablename, target = referenced_table))
|
||||
|
||||
nodes.append(dict(name=tablename, type="table", fields = fields))
|
||||
|
||||
# d3 v4 allows individual modules to be specified. The complete d3 library is included below.
|
||||
response.files.append(URL('admin','static','js/d3.min.js'))
|
||||
response.files.append(URL('admin','static','js/d3_graph.js'))
|
||||
return dict(databases=databases, nodes=nodes, links=links)
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# this file is released under public domain and you can use without limitations
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# This is a sample controller
|
||||
# - index is the default action of any application
|
||||
# - user is required for authentication and authorization
|
||||
# - download is for downloading files uploaded in the db (does streaming)
|
||||
# this file is released under public domain and you can use without limitations
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# ---- example index page ----
|
||||
def index():
|
||||
"""
|
||||
example action using the internationalization operator T and flash
|
||||
rendered by views/default/index.html or views/generic.html
|
||||
|
||||
if you need a simple wiki simply replace the two lines below with:
|
||||
return auth.wiki()
|
||||
"""
|
||||
response.flash = T("Hello World")
|
||||
return dict(message=T('Welcome to web2py!'))
|
||||
|
||||
# ---- API (example) -----
|
||||
@auth.requires_login()
|
||||
def api_get_user_email():
|
||||
if not request.env.request_method == 'GET': raise HTTP(403)
|
||||
return response.json({'status':'success', 'email':auth.user.email})
|
||||
|
||||
# ---- Smart Grid (example) -----
|
||||
@auth.requires_membership('admin') # can only be accessed by members of admin groupd
|
||||
def grid():
|
||||
response.view = 'generic.html' # use a generic view
|
||||
tablename = request.args(0)
|
||||
if not tablename in db.tables: raise HTTP(403)
|
||||
grid = SQLFORM.smartgrid(db[tablename], args=[tablename], deletable=False, editable=False)
|
||||
return dict(grid=grid)
|
||||
|
||||
# ---- Embedded wiki (example) ----
|
||||
def wiki():
|
||||
auth.wikimenu() # add the wiki to the menu
|
||||
return auth.wiki()
|
||||
|
||||
# ---- Action for login/register/etc (required for auth) -----
|
||||
def user():
|
||||
"""
|
||||
exposes:
|
||||
@@ -39,7 +48,7 @@ def user():
|
||||
"""
|
||||
return dict(form=auth())
|
||||
|
||||
|
||||
# ---- action to server uploaded static content (required) ---
|
||||
@cache.action()
|
||||
def download():
|
||||
"""
|
||||
@@ -47,15 +56,3 @@ def download():
|
||||
http://..../[app]/default/download/[filename]
|
||||
"""
|
||||
return response.download(request, db)
|
||||
|
||||
|
||||
def call():
|
||||
"""
|
||||
exposes services. for example:
|
||||
http://..../[app]/default/call/jsonrpc
|
||||
decorate with @services.jsonrpc the functions to expose
|
||||
supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
|
||||
"""
|
||||
return service()
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
'%s %%{row} deleted': '%s %%{fila} %%{eliminada}',
|
||||
'%s %%{row} updated': '%s %%{fila} %%{actualizada}',
|
||||
'%s selected': '%s %%{seleccionado}',
|
||||
'%Y-%m-%d': '%d/%m/%A',
|
||||
'%Y-%m-%d %H:%M:%S': '%d/%m/%A %H:%M:%S',
|
||||
'%Y-%m-%d': '%d/%m/%Y',
|
||||
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||
'(**%.0d MB**)': '(**%.0d MB**)',
|
||||
'(something like "it-it")': '(algo como "it-it")',
|
||||
'**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** %%{item(items)}, **%(bytes)s** %%{byte(bytes)}',
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
'!langcode!': 'fr-ca',
|
||||
'!langname!': 'Français (Canadien)',
|
||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN',
|
||||
'%s %%{row} deleted': '%s rangées supprimées',
|
||||
'%s %%{row} updated': '%s rangées mises à jour',
|
||||
'%s %%{row} deleted': '%s lignes supprimées',
|
||||
'%s %%{row} updated': '%s lignes mises à jour',
|
||||
'%s selected': '%s sélectionné',
|
||||
'%Y-%m-%d': '%Y-%m-%d',
|
||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
||||
@@ -13,12 +13,13 @@
|
||||
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
|
||||
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
|
||||
'?': '?',
|
||||
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
|
||||
'about': 'à propos',
|
||||
'About': 'À propos',
|
||||
'Access Control': "Contrôle d'accès",
|
||||
'admin': 'admin',
|
||||
'Administrative Interface': 'Administrative Interface',
|
||||
'Administrative Interface': "Interface d'administration",
|
||||
'Administrative interface': "Interface d'administration",
|
||||
'Ajax Recipes': 'Recettes Ajax',
|
||||
'An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||
@@ -37,37 +38,39 @@
|
||||
'change password': 'changer le mot de passe',
|
||||
'Check to delete': 'Cliquez pour supprimer',
|
||||
'Check to delete:': 'Cliquez pour supprimer:',
|
||||
'Clear CACHE?': 'Clear CACHE?',
|
||||
'Clear DISK': 'Clear DISK',
|
||||
'Clear RAM': 'Clear RAM',
|
||||
'Clear CACHE?': 'Vider le CACHE?',
|
||||
'Clear DISK': 'Vider le DISQUE',
|
||||
'Clear RAM': 'Vider la RAM',
|
||||
'Client IP': 'IP client',
|
||||
'Community': 'Communauté',
|
||||
'Components and Plugins': 'Components and Plugins',
|
||||
'Components and Plugins': 'Composants et Plugiciels',
|
||||
'Config.ini': 'Config.ini',
|
||||
'Controller': 'Contrôleur',
|
||||
'Copyright': "Droit d'auteur",
|
||||
'Created By': 'Créé par',
|
||||
'Created On': 'Créé le',
|
||||
'Current request': 'Demande actuelle',
|
||||
'Current response': 'Réponse actuelle',
|
||||
'Current session': 'Session en cours',
|
||||
'customize me!': 'personnalisez-moi!',
|
||||
'data uploaded': 'données téléchargées',
|
||||
'Database': 'base de données',
|
||||
'Database %s select': 'base de données %s select',
|
||||
'Database %s select': 'base de données %s selectionnée',
|
||||
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
||||
'db': 'db',
|
||||
'DB Model': 'Modèle DB',
|
||||
'DB Model': 'Modèle BD',
|
||||
'Delete:': 'Supprimer:',
|
||||
'Demo': 'Démo',
|
||||
'Deployment Recipes': 'Recettes de déploiement ',
|
||||
'Description': 'Descriptif',
|
||||
'Deployment Recipes': 'Recettes de déploiement',
|
||||
'Description': 'Description',
|
||||
'design': 'design',
|
||||
'Design': 'Design',
|
||||
'DISK': 'DISK',
|
||||
'DISK': 'DISQUE',
|
||||
'Disk Cache Keys': 'Disk Cache Keys',
|
||||
'Disk Cleared': 'Disk Cleared',
|
||||
'Disk Cleared': 'Disque vidé',
|
||||
'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
|
||||
'Documentation': 'Documentation',
|
||||
"Don't know what to do?": "Don't know what to do?",
|
||||
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
|
||||
'done!': 'fait!',
|
||||
'Download': 'Téléchargement',
|
||||
'E-mail': 'Courriel',
|
||||
@@ -75,26 +78,27 @@
|
||||
'Edit current record': "Modifier l'enregistrement courant",
|
||||
'edit profile': 'modifier le profil',
|
||||
'Edit This App': 'Modifier cette application',
|
||||
'Email and SMS': 'Email and SMS',
|
||||
'Email and SMS': 'Courriel et texto',
|
||||
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g',
|
||||
'enter an integer between %(min)g and %(max)g': 'entrer un entier compris entre %(min)g et %(max)g',
|
||||
'Errors': 'Erreurs',
|
||||
'export as csv file': 'exporter sous forme de fichier csv',
|
||||
'FAQ': 'faq',
|
||||
'FAQ': 'FAQ',
|
||||
'First name': 'Prénom',
|
||||
'Forms and Validators': 'Formulaires et Validateurs',
|
||||
'Free Applications': 'Applications gratuites',
|
||||
'Function disabled': 'Fonction désactivée',
|
||||
'Graph Model': 'Graph Model',
|
||||
'Graph Model': 'Représentation graphique du modèle',
|
||||
'Group %(group_id)s created': '%(group_id)s groupe créé',
|
||||
'Group ID': 'Groupe ID',
|
||||
'Group ID': 'ID du groupe',
|
||||
'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s",
|
||||
'Groups': 'Groupes',
|
||||
'Hello World': 'Bonjour le monde',
|
||||
'Helping web2py': 'Helping web2py',
|
||||
'Helping web2py': 'Aider web2py',
|
||||
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
|
||||
'Home': 'Accueil',
|
||||
'How did you get here?': 'How did you get here?',
|
||||
'import': 'import',
|
||||
'import': 'importer',
|
||||
'Import/Export': 'Importer/Exporter',
|
||||
'Index': 'Index',
|
||||
'insert new': 'insérer un nouveau',
|
||||
@@ -104,40 +108,47 @@
|
||||
'Invalid email': 'Courriel invalide',
|
||||
'Invalid Query': 'Requête Invalide',
|
||||
'invalid request': 'requête invalide',
|
||||
'Key': 'Key',
|
||||
'Is Active': 'Est actif',
|
||||
'Key': 'Clé',
|
||||
'Last name': 'Nom',
|
||||
'Layout': 'Mise en page',
|
||||
'Layout Plugins': 'Layout Plugins',
|
||||
'Layouts': 'layouts',
|
||||
'Layout Plugins': 'Plugins de mise en page',
|
||||
'Layouts': 'Mises en page',
|
||||
'Live chat': 'Clavardage en direct',
|
||||
'Live Chat': 'Live Chat',
|
||||
'Log In': 'Log In',
|
||||
'Live Chat': 'Clavardage en direct',
|
||||
'Loading...': 'Chargement...',
|
||||
'loading...': 'chargement...',
|
||||
'Log In': 'Connexion',
|
||||
'Logged in': 'Connecté',
|
||||
'login': 'connectez-vous',
|
||||
'Login': 'Connectez-vous',
|
||||
'logout': 'déconnectez-vous',
|
||||
'login': 'connexion',
|
||||
'Login': 'Connexion',
|
||||
'logout': 'déconnexion',
|
||||
'lost password': 'mot de passe perdu',
|
||||
'Lost Password': 'Mot de passe perdu',
|
||||
'Lost password?': 'Mot de passe perdu?',
|
||||
'lost password?': 'mot de passe perdu?',
|
||||
'Main Menu': 'Menu principal',
|
||||
'Manage %(action)s': 'Manage %(action)s',
|
||||
'Manage Access Control': 'Manage Access Control',
|
||||
'Manage Cache': 'Manage Cache',
|
||||
'Manage Cache': 'Gérer le Cache',
|
||||
'Memberships': 'Memberships',
|
||||
'Menu Model': 'Menu modèle',
|
||||
'My Sites': 'My Sites',
|
||||
'Modified By': 'Modifié par',
|
||||
'Modified On': 'Modifié le',
|
||||
'My Sites': 'Mes sites',
|
||||
'Name': 'Nom',
|
||||
'New Record': 'Nouvel enregistrement',
|
||||
'new record inserted': 'nouvel enregistrement inséré',
|
||||
'next %s rows': 'next %s rows',
|
||||
'next %s rows': '%s prochaine lignes',
|
||||
'next 100 rows': '100 prochaines lignes',
|
||||
'No databases in this application': "Cette application n'a pas de bases de données",
|
||||
'Number of entries: **%s**': 'Number of entries: **%s**',
|
||||
'Object or table name': 'Objet ou nom de table',
|
||||
'Online book': 'Online book',
|
||||
'Online examples': 'Exemples en ligne',
|
||||
'or import from csv file': "ou importer d'un fichier CSV",
|
||||
'Origin': 'Origine',
|
||||
'Other Plugins': 'Other Plugins',
|
||||
'Other Plugins': 'Autres Plugiciels',
|
||||
'Other Recipes': 'Autres recettes',
|
||||
'Overview': 'Présentation',
|
||||
'password': 'mot de passe',
|
||||
@@ -145,33 +156,34 @@
|
||||
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
||||
'Permission': 'Permission',
|
||||
'Permissions': 'Permissions',
|
||||
'please input your password again': "S'il vous plaît entrer votre mot de passe",
|
||||
'please input your password again': "S'il vous plaît entrer votre mot de passe à nouveau",
|
||||
'Plugins': 'Plugiciels',
|
||||
'Powered by': 'Alimenté par',
|
||||
'Preface': 'Préface',
|
||||
'previous %s rows': 'previous %s rows',
|
||||
'previous %s rows': '%s lignes précédentes',
|
||||
'previous 100 rows': '100 lignes précédentes',
|
||||
'profile': 'profile',
|
||||
'pygraphviz library not found': 'pygraphviz library not found',
|
||||
'profile': 'profil',
|
||||
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
|
||||
'Python': 'Python',
|
||||
'Query:': 'Requête:',
|
||||
'Quick Examples': 'Examples Rapides',
|
||||
'Quick Examples': 'Exemples Rapides',
|
||||
'RAM': 'RAM',
|
||||
'RAM Cache Keys': 'RAM Cache Keys',
|
||||
'Ram Cleared': 'Ram Cleared',
|
||||
'Ram Cleared': 'Ram vidée',
|
||||
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
|
||||
'Readme': 'Lisez-moi',
|
||||
'Recipes': 'Recettes',
|
||||
'Record': 'enregistrement',
|
||||
'Record %(id)s created': 'Record %(id)s created',
|
||||
'Record %(id)s updated': 'Record %(id)s updated',
|
||||
'Record Created': 'Record Created',
|
||||
'Record %(id)s created': 'Enregistrement %(id)s créé',
|
||||
'Record %(id)s updated': 'Enregistrement %(id)s modifié',
|
||||
'Record Created': 'Enregistrement créé',
|
||||
'record does not exist': "l'archive n'existe pas",
|
||||
'Record ID': "ID d'enregistrement",
|
||||
'Record id': "id d'enregistrement",
|
||||
'Record Updated': 'Record Updated',
|
||||
'Record ID': "ID de l'enregistrement",
|
||||
'Record id': "id de l'enregistrement",
|
||||
'Record Updated': 'Enregistrement modifié',
|
||||
'Register': "S'inscrire",
|
||||
'register': "s'inscrire",
|
||||
'Registration identifier': "Identifiant d'inscription",
|
||||
'Registration key': "Clé d'enregistrement",
|
||||
'Registration successful': 'Inscription réussie',
|
||||
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
||||
@@ -179,18 +191,18 @@
|
||||
'Reset Password key': 'Réinitialiser le mot clé',
|
||||
'Resources': 'Ressources',
|
||||
'Role': 'Rôle',
|
||||
'Roles': 'Roles',
|
||||
'Roles': 'Rôles',
|
||||
'Rows in Table': 'Lignes du tableau',
|
||||
'Rows selected': 'Lignes sélectionnées',
|
||||
'Save model as...': 'Save model as...',
|
||||
'Save model as...': 'Enregistrer le modèle sous...',
|
||||
'Semantic': 'Sémantique',
|
||||
'Services': 'Services',
|
||||
'Sign Up': 'Sign Up',
|
||||
'Size of cache:': 'Size of cache:',
|
||||
'Sign Up': "S'inscrire",
|
||||
'Size of cache:': 'Taille de la mémoire cache:',
|
||||
'state': 'état',
|
||||
'Statistics': 'Statistics',
|
||||
'Statistics': 'Statistiques',
|
||||
'Stylesheet': 'Feuille de style',
|
||||
'submit': 'submit',
|
||||
'submit': 'soumettre',
|
||||
'Submit': 'Soumettre',
|
||||
'Support': 'Soutien',
|
||||
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||
@@ -202,31 +214,31 @@
|
||||
'The Views': 'Les Vues',
|
||||
'This App': 'Cette Appli',
|
||||
'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage",
|
||||
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
||||
'Time in Cache (h:m:s)': 'Temps en Cache (h:m:s)',
|
||||
'Timestamp': 'Horodatage',
|
||||
'Traceback': 'Traceback',
|
||||
'Twitter': 'Twitter',
|
||||
'unable to parse csv file': "incapable d'analyser le fichier cvs",
|
||||
'Update:': 'Mise à jour:',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.',
|
||||
'User': 'User',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT afin de construire des requêtes plus complexes.',
|
||||
'User': 'Utilisateur',
|
||||
'User %(id)s Logged-in': 'Utilisateur %(id)s connecté',
|
||||
'User %(id)s Registered': 'Utilisateur %(id)s enregistré',
|
||||
'User ID': 'ID utilisateur',
|
||||
'User Voice': 'User Voice',
|
||||
'Users': 'Users',
|
||||
'value already in database or empty': 'valeur déjà dans la base ou vide',
|
||||
'User Voice': "Voix de l'utilisateur",
|
||||
'Users': 'Utilisateurs',
|
||||
'value already in database or empty': 'valeur déjà dans la base ou inexistante',
|
||||
'Verify Password': 'Vérifiez le mot de passe',
|
||||
'Videos': 'Vidéos',
|
||||
'View': 'Présentation',
|
||||
'View': 'Vue',
|
||||
'Web2py': 'Web2py',
|
||||
'Welcome': 'Bienvenu',
|
||||
'Welcome': 'Bienvenue',
|
||||
'Welcome %s': 'Bienvenue %s',
|
||||
'Welcome to web2py': 'Bienvenue à web2py',
|
||||
'Welcome to web2py!': 'Welcome to web2py!',
|
||||
'Welcome to web2py!': 'Bienvenue à web2py!',
|
||||
'Which called the function %s located in the file %s': 'Qui a appelé la fonction %s se trouvant dans le fichier %s',
|
||||
'Working...': 'Working...',
|
||||
'You are successfully running web2py': 'Vous roulez avec succès web2py',
|
||||
'Working...': 'Traitement en cours...',
|
||||
'You are successfully running web2py': 'Vous exécutez avec succès web2py',
|
||||
'You can modify this application and adapt it to your needs': "Vous pouvez modifier cette application et l'adapter à vos besoins",
|
||||
'You visited the url %s': "Vous avez visité l'URL %s",
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
|
||||
'**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
|
||||
'?': '?',
|
||||
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||
'``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)': '``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)',
|
||||
'about': 'à propos',
|
||||
'About': 'À propos',
|
||||
'Access Control': "Contrôle d'accès",
|
||||
'admin': 'admin',
|
||||
@@ -31,7 +33,7 @@
|
||||
'Cache': 'Cache',
|
||||
'Cache Cleared': 'Cache Cleared',
|
||||
'Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
|
||||
'Cache Keys': 'Clés de cache',
|
||||
'Cache Keys': 'Cache Keys',
|
||||
'Cannot be empty': 'Ne peut pas être vide',
|
||||
'change password': 'changer le mot de passe',
|
||||
'Check to delete': 'Cliquez pour supprimer',
|
||||
@@ -41,10 +43,10 @@
|
||||
'Clear RAM': 'Vider la RAM',
|
||||
'Client IP': 'IP client',
|
||||
'Community': 'Communauté',
|
||||
'Components and Plugins': 'Composants et Plugins',
|
||||
'Components and Plugins': 'Composants et Plugiciels',
|
||||
'Config.ini': 'Config.ini',
|
||||
'Controller': 'Contrôleur',
|
||||
'Copyright': 'Copyright',
|
||||
'Copyright': "Droit d'auteur",
|
||||
'Created By': 'Créé par',
|
||||
'Created On': 'Créé le',
|
||||
'Current request': 'Demande actuelle',
|
||||
@@ -55,8 +57,8 @@
|
||||
'Database': 'base de données',
|
||||
'Database %s select': 'base de données %s selectionnée',
|
||||
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
||||
'db': 'bdd',
|
||||
'DB Model': 'Modèle BDD',
|
||||
'db': 'db',
|
||||
'DB Model': 'Modèle BD',
|
||||
'Delete:': 'Supprimer:',
|
||||
'Demo': 'Démo',
|
||||
'Deployment Recipes': 'Recettes de déploiement',
|
||||
@@ -71,12 +73,13 @@
|
||||
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
|
||||
'done!': 'fait!',
|
||||
'Download': 'Téléchargement',
|
||||
'E-mail': 'E-mail',
|
||||
'E-mail': 'Courriel',
|
||||
'Edit': 'Éditer',
|
||||
'Edit current record': "Modifier l'enregistrement courant",
|
||||
'edit profile': 'modifier le profil',
|
||||
'Edit This App': 'Modifier cette application',
|
||||
'Email and SMS': 'Email et SMS',
|
||||
'Email and SMS': 'Courriel et texto',
|
||||
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g',
|
||||
'enter an integer between %(min)g and %(max)g': 'entrez un entier entre %(min)g et %(max)g',
|
||||
'Errors': 'Erreurs',
|
||||
'export as csv file': 'exporter sous forme de fichier csv',
|
||||
@@ -85,22 +88,24 @@
|
||||
'Forms and Validators': 'Formulaires et Validateurs',
|
||||
'Free Applications': 'Applications gratuites',
|
||||
'Function disabled': 'Fonction désactivée',
|
||||
'Graph Model': 'Graph Model',
|
||||
'Group ID': 'Groupe ID',
|
||||
'Graph Model': 'Représentation graphique du modèle',
|
||||
'Group %(group_id)s created': '%(group_id)s groupe créé',
|
||||
'Group ID': 'ID du groupe',
|
||||
'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s",
|
||||
'Groups': 'Groupes',
|
||||
'Hello World': 'Bonjour le monde',
|
||||
'Helping web2py': 'En train d’aider web2py',
|
||||
'Helping web2py': 'Aider web2py',
|
||||
'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})': 'Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})',
|
||||
'Home': 'Accueil',
|
||||
'How did you get here?': 'Comment êtes-vous arrivé ici?',
|
||||
'import': 'Importer',
|
||||
'How did you get here?': 'How did you get here?',
|
||||
'import': 'importer',
|
||||
'Import/Export': 'Importer/Exporter',
|
||||
'Index': 'Index',
|
||||
'insert new': 'insérer un nouveau',
|
||||
'insert new %s': 'insérer un nouveau %s',
|
||||
'Internal State': 'État interne',
|
||||
'Introduction': 'Introduction',
|
||||
'Invalid email': 'E-mail invalide',
|
||||
'Introduction': 'Présentation',
|
||||
'Invalid email': 'Courriel invalide',
|
||||
'Invalid Query': 'Requête Invalide',
|
||||
'invalid request': 'requête invalide',
|
||||
'Is Active': 'Est actif',
|
||||
@@ -109,12 +114,15 @@
|
||||
'Layout': 'Mise en page',
|
||||
'Layout Plugins': 'Plugins de mise en page',
|
||||
'Layouts': 'Mises en page',
|
||||
'Live chat': 'Chat en direct',
|
||||
'Live Chat': 'Chat en direct',
|
||||
'Log In': 'Log In',
|
||||
'login': 'connectez-vous',
|
||||
'Login': 'Connectez-vous',
|
||||
'logout': 'déconnectez-vous',
|
||||
'Live chat': 'Clavardage en direct',
|
||||
'Live Chat': 'Clavardage en direct',
|
||||
'Loading...': 'Chargement...',
|
||||
'loading...': 'chargement...',
|
||||
'Log In': 'Connexion',
|
||||
'Logged in': 'Connecté',
|
||||
'login': 'connexion',
|
||||
'Login': 'Connexion',
|
||||
'logout': 'déconnexion',
|
||||
'lost password': 'mot de passe perdu',
|
||||
'Lost Password': 'Mot de passe perdu',
|
||||
'Lost password?': 'Mot de passe perdu?',
|
||||
@@ -131,7 +139,7 @@
|
||||
'Name': 'Nom',
|
||||
'New Record': 'Nouvel enregistrement',
|
||||
'new record inserted': 'nouvel enregistrement inséré',
|
||||
'next %s rows': 'next %s rows',
|
||||
'next %s rows': '%s prochaine lignes',
|
||||
'next 100 rows': '100 prochaines lignes',
|
||||
'No databases in this application': "Cette application n'a pas de bases de données",
|
||||
'Number of entries: **%s**': 'Number of entries: **%s**',
|
||||
@@ -140,55 +148,63 @@
|
||||
'Online examples': 'Exemples en ligne',
|
||||
'or import from csv file': "ou importer d'un fichier CSV",
|
||||
'Origin': 'Origine',
|
||||
'Other Plugins': 'Autres Plugins',
|
||||
'Other Plugins': 'Autres Plugiciels',
|
||||
'Other Recipes': 'Autres recettes',
|
||||
'Overview': 'Présentation',
|
||||
'password': 'mot de passe',
|
||||
'Password': 'Mot de passe',
|
||||
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
||||
'Permission': 'Permission',
|
||||
'Permissions': 'Permissions',
|
||||
'Plugins': 'Plugins',
|
||||
'please input your password again': "S'il vous plaît entrer votre mot de passe à nouveau",
|
||||
'Plugins': 'Plugiciels',
|
||||
'Powered by': 'Alimenté par',
|
||||
'Preface': 'Préface',
|
||||
'previous %s rows': 'previous %s rows',
|
||||
'previous %s rows': '%s lignes précédentes',
|
||||
'previous 100 rows': '100 lignes précédentes',
|
||||
'profile': 'profil',
|
||||
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
|
||||
'Python': 'Python',
|
||||
'Query:': 'Requête:',
|
||||
'Quick Examples': 'Exemples Rapides',
|
||||
'RAM': 'RAM',
|
||||
'RAM Cache Keys': 'Clés de cache de la RAM',
|
||||
'RAM Cache Keys': 'RAM Cache Keys',
|
||||
'Ram Cleared': 'Ram vidée',
|
||||
'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.': 'RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.',
|
||||
'Readme': 'Lisez-moi',
|
||||
'Recipes': 'Recettes',
|
||||
'Record': 'enregistrement',
|
||||
'Record %(id)s created': 'Enregistrement %(id)s créé',
|
||||
'Record %(id)s updated': 'Enregistrement %(id)s modifié',
|
||||
'Record Created': 'Enregistrement créé',
|
||||
'record does not exist': "l'archive n'existe pas",
|
||||
'Record ID': "ID d'enregistrement",
|
||||
'Record id': "id d'enregistrement",
|
||||
'Record ID': "ID de l'enregistrement",
|
||||
'Record id': "id de l'enregistrement",
|
||||
'Record Updated': 'Enregistrement modifié',
|
||||
'Register': "S'inscrire",
|
||||
'register': "s'inscrire",
|
||||
'Registration identifier': "Identifiant d'enregistrement",
|
||||
'Registration identifier': "Identifiant d'inscription",
|
||||
'Registration key': "Clé d'enregistrement",
|
||||
'Registration successful': 'Inscription réussie',
|
||||
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
||||
'Request reset password': 'Demande de réinitialiser le mot clé',
|
||||
'Reset Password key': 'Réinitialiser le mot clé',
|
||||
'Resources': 'Ressources',
|
||||
'Role': 'Rôle',
|
||||
'Roles': 'Roles',
|
||||
'Roles': 'Rôles',
|
||||
'Rows in Table': 'Lignes du tableau',
|
||||
'Rows selected': 'Lignes sélectionnées',
|
||||
'Save model as...': 'Enregistrer le modèle sous...',
|
||||
'Semantic': 'Sémantique',
|
||||
'Services': 'Services',
|
||||
'Sign Up': 'Sign Up',
|
||||
'Size of cache:': 'Taille du cache:',
|
||||
'Sign Up': "S'inscrire",
|
||||
'Size of cache:': 'Taille de la mémoire cache:',
|
||||
'state': 'état',
|
||||
'Statistics': 'Statistiques',
|
||||
'Stylesheet': 'Feuille de style',
|
||||
'submit': 'soumettre',
|
||||
'Submit': 'Soumettre',
|
||||
'Support': 'Support',
|
||||
'Support': 'Soutien',
|
||||
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||
'Table': 'tableau',
|
||||
'Table name': 'Nom du tableau',
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# AppConfig configuration made easy. Look inside private/appconfig.ini
|
||||
# Auth is for authenticaiton and access control
|
||||
# -------------------------------------------------------------------------
|
||||
from gluon.contrib.appconfig import AppConfig
|
||||
from gluon.tools import Auth
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# This scaffolding model makes your app work on Google App Engine too
|
||||
# File is released under public domain and you can use without limitations
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
if request.global_settings.web2py_version < "2.14.1":
|
||||
raise HTTP(500, "Requires web2py 2.13.3 or newer")
|
||||
if request.global_settings.web2py_version < "2.15.5":
|
||||
raise HTTP(500, "Requires web2py 2.15.5 or newer")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# if SSL/HTTPS is properly configured and you want all HTTP requests to
|
||||
@@ -14,23 +21,18 @@ if request.global_settings.web2py_version < "2.14.1":
|
||||
# -------------------------------------------------------------------------
|
||||
# request.requires_https()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# app configuration made easy. Look inside private/appconfig.ini
|
||||
# -------------------------------------------------------------------------
|
||||
from gluon.contrib.appconfig import AppConfig
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# once in production, remove reload=True to gain full speed
|
||||
# -------------------------------------------------------------------------
|
||||
myconf = AppConfig(reload=True)
|
||||
configuration = AppConfig(reload=True)
|
||||
|
||||
if not request.env.web2py_runtime_gae:
|
||||
# ---------------------------------------------------------------------
|
||||
# if NOT running on Google App Engine use SQLite or other DB
|
||||
# ---------------------------------------------------------------------
|
||||
db = DAL(myconf.get('db.uri'),
|
||||
pool_size=myconf.get('db.pool_size'),
|
||||
migrate_enabled=myconf.get('db.migrate'),
|
||||
db = DAL(configuration.get('db.uri'),
|
||||
pool_size=configuration.get('db.pool_size'),
|
||||
migrate_enabled=configuration.get('db.migrate'),
|
||||
check_reserved=['all'])
|
||||
else:
|
||||
# ---------------------------------------------------------------------
|
||||
@@ -52,12 +54,15 @@ else:
|
||||
# by default give a view/generic.extension to all actions from localhost
|
||||
# none otherwise. a pattern can be 'controller/function.extension'
|
||||
# -------------------------------------------------------------------------
|
||||
response.generic_patterns = ['*'] if request.is_local else []
|
||||
response.generic_patterns = []
|
||||
if request.is_local and not configuration.get('app.production'):
|
||||
response.generic_patterns.append('*')
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# choose a style for forms
|
||||
# -------------------------------------------------------------------------
|
||||
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
|
||||
response.form_label_separator = myconf.get('forms.separator') or ''
|
||||
response.formstyle = 'bootstrap4_inline'
|
||||
response.form_label_separator = ''
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# (optional) optimize handling of static files
|
||||
@@ -80,27 +85,24 @@ response.form_label_separator = myconf.get('forms.separator') or ''
|
||||
# (more options discussed in gluon/tools.py)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
from gluon.tools import Auth, Service, PluginManager
|
||||
|
||||
# host names must be a list of allowed host names (glob syntax allowed)
|
||||
auth = Auth(db, host_names=myconf.get('host.names'))
|
||||
service = Service()
|
||||
plugins = PluginManager()
|
||||
auth = Auth(db, host_names=configuration.get('host.names'))
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# create all tables needed by auth if not custom tables
|
||||
# create all tables needed by auth, maybe add a list of extra fields
|
||||
# -------------------------------------------------------------------------
|
||||
auth.settings.extra_fields['auth_user'] = []
|
||||
auth.define_tables(username=False, signature=False)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# configure email
|
||||
# -------------------------------------------------------------------------
|
||||
mail = auth.settings.mailer
|
||||
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
|
||||
mail.settings.sender = myconf.get('smtp.sender')
|
||||
mail.settings.login = myconf.get('smtp.login')
|
||||
mail.settings.tls = myconf.get('smtp.tls') or False
|
||||
mail.settings.ssl = myconf.get('smtp.ssl') or False
|
||||
mail.settings.server = 'logging' if request.is_local else configuration.get('smtp.server')
|
||||
mail.settings.sender = configuration.get('smtp.sender')
|
||||
mail.settings.login = configuration.get('smtp.login')
|
||||
mail.settings.tls = configuration.get('smtp.tls') or False
|
||||
mail.settings.ssl = configuration.get('smtp.ssl') or False
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# configure auth policy
|
||||
@@ -109,6 +111,27 @@ auth.settings.registration_requires_verification = False
|
||||
auth.settings.registration_requires_approval = False
|
||||
auth.settings.reset_password_requires_verification = True
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# read more at http://dev.w3.org/html5/markup/meta.name.html
|
||||
# -------------------------------------------------------------------------
|
||||
response.meta.author = configuration.get('app.author')
|
||||
response.meta.description = configuration.get('app.description')
|
||||
response.meta.keywords = configuration.get('app.keywords')
|
||||
response.meta.generator = configuration.get('app.generator')
|
||||
response.show_toolbar = configuration.get('app.toolbar')
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# your http://google.com/analytics id
|
||||
# -------------------------------------------------------------------------
|
||||
response.google_analytics_id = configuration.get('google.analytics_id')
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# maybe use the scheduler
|
||||
# -------------------------------------------------------------------------
|
||||
if configuration.get('scheduler.enabled'):
|
||||
from gluon.scheduler import Scheduler
|
||||
scheduler = Scheduler(db, heartbeat=configuration.get('heartbeat'))
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Define your tables below (or better in another model file) for example
|
||||
#
|
||||
|
||||
@@ -1,29 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# this file is released under public domain and you can use without limitations
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# Customize your APP title, subtitle and menus here
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
response.logo = A(B('web', SPAN(2), 'py'), XML('™ '),
|
||||
_class="navbar-brand", _href="http://www.web2py.com/",
|
||||
_id="web2py-logo")
|
||||
response.title = request.application.replace('_', ' ').title()
|
||||
response.subtitle = ''
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# read more at http://dev.w3.org/html5/markup/meta.name.html
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
response.meta.author = myconf.get('app.author')
|
||||
response.meta.description = myconf.get('app.description')
|
||||
response.meta.keywords = myconf.get('app.keywords')
|
||||
response.meta.generator = myconf.get('app.generator')
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# your http://google.com/analytics id
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
response.google_analytics_id = None
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# this is the main application menu add/remove items as required
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
@@ -32,53 +9,42 @@ response.menu = [
|
||||
(T('Home'), False, URL('default', 'index'), [])
|
||||
]
|
||||
|
||||
DEVELOPMENT_MENU = True
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# provide shortcuts for development. remove in production
|
||||
# provide shortcuts for development. you can remove everything below in production
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _():
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
# shortcuts
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
app = request.application
|
||||
ctr = request.controller
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
# useful links to internal and external resources
|
||||
# ------------------------------------------------------------------------------------------------------------------
|
||||
if not configuration.get('app.production'):
|
||||
_app = request.application
|
||||
response.menu += [
|
||||
(T('My Sites'), False, URL('admin', 'default', 'site')),
|
||||
(T('This App'), False, '#', [
|
||||
(T('Design'), False, URL('admin', 'default', 'design/%s' % app)),
|
||||
LI(_class="divider"),
|
||||
(T('Design'), False, URL('admin', 'default', 'design/%s' % _app)),
|
||||
(T('Controller'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/controllers/%s.py' % (app, ctr))),
|
||||
'admin', 'default', 'edit/%s/controllers/%s.py' % (_app, request.controller))),
|
||||
(T('View'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/views/%s' % (app, response.view))),
|
||||
'admin', 'default', 'edit/%s/views/%s' % (_app, response.view))),
|
||||
(T('DB Model'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/models/db.py' % app)),
|
||||
'admin', 'default', 'edit/%s/models/db.py' % _app)),
|
||||
(T('Menu Model'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/models/menu.py' % app)),
|
||||
'admin', 'default', 'edit/%s/models/menu.py' % _app)),
|
||||
(T('Config.ini'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/private/appconfig.ini' % app)),
|
||||
'admin', 'default', 'edit/%s/private/appconfig.ini' % _app)),
|
||||
(T('Layout'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/views/layout.html' % app)),
|
||||
'admin', 'default', 'edit/%s/views/layout.html' % _app)),
|
||||
(T('Stylesheet'), False,
|
||||
URL(
|
||||
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % app)),
|
||||
(T('Database'), False, URL(app, 'appadmin', 'index')),
|
||||
'admin', 'default', 'edit/%s/static/css/web2py-bootstrap3.css' % _app)),
|
||||
(T('Database'), False, URL(_app, 'appadmin', 'index')),
|
||||
(T('Errors'), False, URL(
|
||||
'admin', 'default', 'errors/' + app)),
|
||||
'admin', 'default', 'errors/' + _app)),
|
||||
(T('About'), False, URL(
|
||||
'admin', 'default', 'about/' + app)),
|
||||
'admin', 'default', 'about/' + _app)),
|
||||
]),
|
||||
('web2py.com', False, '#', [
|
||||
(T('Download'), False,
|
||||
@@ -98,7 +64,6 @@ def _():
|
||||
]),
|
||||
(T('Documentation'), False, '#', [
|
||||
(T('Online book'), False, 'http://www.web2py.com/book'),
|
||||
LI(_class="divider"),
|
||||
(T('Preface'), False,
|
||||
'http://www.web2py.com/book/default/chapter/00'),
|
||||
(T('Introduction'), False,
|
||||
@@ -143,9 +108,3 @@ def _():
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
if DEVELOPMENT_MENU:
|
||||
_()
|
||||
|
||||
if "auth" in locals():
|
||||
auth.wikimenu()
|
||||
|
||||
@@ -5,6 +5,8 @@ author = Your Name <you@example.com>
|
||||
description = a cool new app
|
||||
keywords = web2py, python, framework
|
||||
generator = Web2py Web Framework
|
||||
production = false
|
||||
toolbar = false
|
||||
|
||||
; Host configuration
|
||||
[host]
|
||||
@@ -14,7 +16,6 @@ names = localhost:*, 127.0.0.1:*, *:*, *
|
||||
[db]
|
||||
uri = sqlite://storage.sqlite
|
||||
migrate = true
|
||||
; ignored for sqlite
|
||||
pool_size = 10
|
||||
|
||||
; smtp address and credentials
|
||||
@@ -25,7 +26,9 @@ login = username:password
|
||||
tls = true
|
||||
ssl = true
|
||||
|
||||
; form styling
|
||||
[forms]
|
||||
formstyle = bootstrap3_inline
|
||||
separator =
|
||||
[scheduler]
|
||||
enabled = false
|
||||
heartbeat = 1
|
||||
|
||||
[google]
|
||||
analytics_id =
|
||||
@@ -20,8 +20,8 @@
|
||||
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
from fileutils import abspath
|
||||
from languages import read_possible_languages
|
||||
from gluon.fileutils import abspath
|
||||
from gluon.languages import read_possible_languages
|
||||
|
||||
possible_languages = read_possible_languages(abspath('applications', app))
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,11 @@
|
||||
ul.navbar-nav.navbar-right {
|
||||
position: absolute;
|
||||
right: 100px;
|
||||
}
|
||||
label, th {
|
||||
font-weigth: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
div.w2p_flash {
|
||||
background-image: none;
|
||||
border-radius: 4px;
|
||||
@@ -108,7 +116,6 @@ select.autocomplete {
|
||||
background: url(../images/background.jpg) no-repeat center center;
|
||||
}
|
||||
body {
|
||||
padding-top: 60px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
header {
|
||||
@@ -126,8 +133,7 @@ html {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #333;
|
||||
color: #aaa;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
header h1 {
|
||||
color: #FFF!important;
|
||||
@@ -315,3 +321,20 @@ td.w2p_fc,
|
||||
input[type=checkbox], input[type=radio] {
|
||||
margin: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
/* for backward compatbility with pre-font-awesome */
|
||||
.icon.plus,.icon.arrowleft,.icon.download,.icon.trash,.icon.pen,.icon.arrowright,.icon.magnifier {
|
||||
display: inline-block;
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
font-size: inherit;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.icon.plus:before { content: "\f067";}
|
||||
.icon.arrowleft:before { content: "\f060";}
|
||||
.icon.download:before { content: "\f019";}
|
||||
.icon.trash:before { content: "\f1f8";}
|
||||
.icon.pen:before { content: "\f040";}
|
||||
.icon.arrowright:before { content: "\f061";}
|
||||
.icon.magnifier:before { content: "\f002";}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 724 KiB |
10
applications/welcome/static/js/bootstrap.min.js
vendored
10
applications/welcome/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
9
applications/welcome/static/js/jquery.js
vendored
9
applications/welcome/static/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
@@ -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!');
|
||||
}
|
||||
|
||||
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||
var animateIn = 'fadeIn';
|
||||
// var animateIn = 'slideDown';
|
||||
|
||||
String.prototype.reverse = function () {
|
||||
return this.split('').reverse().join('');
|
||||
};
|
||||
@@ -38,8 +42,12 @@
|
||||
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
||||
else $('#' + id).show().fadeOut('slow');
|
||||
},
|
||||
ajax: function (u, s, t) {
|
||||
ajax: function (u, s, t, options) {
|
||||
/*simple ajax function*/
|
||||
|
||||
// set options default value
|
||||
options = typeof options !== 'undefined' ? options : {};
|
||||
|
||||
var query = '';
|
||||
if (typeof s == 'string') {
|
||||
var d = $(s).serialize();
|
||||
@@ -59,18 +67,44 @@
|
||||
query = pcs.join('&');
|
||||
}
|
||||
}
|
||||
$.ajax({
|
||||
|
||||
// default success action
|
||||
var success_function = function (msg) {
|
||||
if (t) {
|
||||
if (t == ':eval') eval(msg);
|
||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
||||
else t(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// declare success actions as array
|
||||
var success = [success_function];
|
||||
|
||||
// add user success actions
|
||||
if ($.isArray(options.done)){
|
||||
success = $.merge(success, options.done);
|
||||
} else {
|
||||
success.push(options.done);
|
||||
}
|
||||
|
||||
// default jquery ajax options
|
||||
var ajax_options = {
|
||||
type: 'POST',
|
||||
url: u,
|
||||
data: query,
|
||||
success: function (msg) {
|
||||
if (t) {
|
||||
if (t == ':eval') eval(msg);
|
||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
||||
else t(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
success: success
|
||||
};
|
||||
|
||||
//remove custom "done" option if exists
|
||||
delete options.done;
|
||||
|
||||
// merge default ajax options with user custom options
|
||||
for (var attrname in options) {
|
||||
ajax_options[attrname] = options[attrname];
|
||||
}
|
||||
|
||||
// call ajax function
|
||||
$.ajax(ajax_options);
|
||||
},
|
||||
ajax_fields: function (target) {
|
||||
/*
|
||||
@@ -148,7 +182,7 @@
|
||||
},
|
||||
/* manage errors in forms */
|
||||
manage_errors: function (target) {
|
||||
$('div.error', target).hide().slideDown('slow');
|
||||
$('div.error', target).hide()[animateIn]('slow');
|
||||
},
|
||||
after_ajax: function (xhr) {
|
||||
/* called whenever an ajax request completes */
|
||||
@@ -233,13 +267,17 @@
|
||||
}
|
||||
});
|
||||
/* help preventing double form submission for normal form (not LOADed) */
|
||||
$(doc).on('submit', 'form', function () {
|
||||
var submit_button = $(this).find(web2py.formInputClickSelector);
|
||||
web2py.disableElement(submit_button);
|
||||
$(doc).on('submit', 'form', function (e) {
|
||||
var submit_buttons = $(this).find(web2py.formInputClickSelector);
|
||||
submit_buttons.each(function() {
|
||||
web2py.disableElement($(this));
|
||||
})
|
||||
/* safeguard in case the form doesn't trigger a refresh,
|
||||
see https://github.com/web2py/web2py/issues/1100 */
|
||||
setTimeout(function () {
|
||||
web2py.enableElement(submit_button);
|
||||
submit_buttons.each(function() {
|
||||
web2py.enableElement($(this));
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
doc.ajaxSuccess(function (e, xhr) {
|
||||
@@ -290,7 +328,15 @@
|
||||
form.submit(function (e) {
|
||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||
web2py.hide_flash();
|
||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
||||
|
||||
var formData;
|
||||
if (FORMDATA_IS_SUPPORTED) {
|
||||
formData = new FormData(form[0]); // Allows file uploads.
|
||||
} else {
|
||||
formData = form.serialize(); // Fallback for older browsers.
|
||||
}
|
||||
web2py.ajax_page('post', url, formData, target, form);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||
@@ -309,11 +355,18 @@
|
||||
if (web2py.isUndefined(element)) element = $(document);
|
||||
/* if target is not there, fill it with something that there isn't in the page*/
|
||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||
|
||||
/* processData and contentType must be set to false when passing a FormData
|
||||
object to jQuery.ajax. */
|
||||
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||
$.ajax({
|
||||
'type': method,
|
||||
'url': action,
|
||||
'data': data,
|
||||
'processData': !isFormData,
|
||||
'contentType': contentType,
|
||||
'beforeSend': function (xhr, settings) {
|
||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||
xhr.setRequestHeader('web2py-component-element', target);
|
||||
@@ -564,8 +617,8 @@
|
||||
flash: function (message, status) {
|
||||
var flash = $('.w2p_flash');
|
||||
web2py.hide_flash();
|
||||
flash.html(message).addClass(status);
|
||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
||||
flash.text(message).addClass(status);
|
||||
if (flash.html()) flash.append('<span id="closeflash"> × </span>')[animateIn]();
|
||||
},
|
||||
hide_flash: function () {
|
||||
$('.w2p_flash').fadeOut(0).html('');
|
||||
@@ -579,7 +632,7 @@
|
||||
for (var k = 0; k < triggers[id].length; k++) {
|
||||
var dep = $('#' + triggers[id][k], target);
|
||||
var tr = $('#' + triggers[id][k] + '__row', target);
|
||||
if (t.is(dep.attr('data-show-if'))) tr.slideDown();
|
||||
if (t.is(dep.attr('data-show-if'))) tr[animateIn]();
|
||||
else tr.hide();
|
||||
}
|
||||
};
|
||||
@@ -669,8 +722,9 @@
|
||||
});
|
||||
},
|
||||
/* Disables form elements:
|
||||
- Does not disable elements with 'data-w2p_disable' attribute
|
||||
- Caches element value in 'w2p_enable_with' data store
|
||||
- Replaces element text with value of 'data-disable-with' attribute
|
||||
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||
- Sets disabled property to true
|
||||
*/
|
||||
disableFormElements: function (form) {
|
||||
@@ -682,13 +736,15 @@
|
||||
if (!web2py.isUndefined(disable)) {
|
||||
return false;
|
||||
}
|
||||
if (web2py.isUndefined(disable_with)) {
|
||||
element.data('w2p_disable_with', element[method]());
|
||||
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||
if (web2py.isUndefined(disable_with)) {
|
||||
element.data('w2p_disable_with', element[method]());
|
||||
}
|
||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
}
|
||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||
element.data('w2p_enable_with', element[method]());
|
||||
}
|
||||
element[method](element.data('w2p_disable_with'));
|
||||
element.prop('disabled', true);
|
||||
});
|
||||
},
|
||||
@@ -769,4 +825,4 @@ web2py_event_handlers = jQuery.web2py.event_handlers;
|
||||
web2py_trap_link = jQuery.web2py.trap_link;
|
||||
web2py_calc_entropy = jQuery.web2py.calc_entropy;
|
||||
*/
|
||||
/* compatibility code - end*/
|
||||
/* compatibility code - end*/
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
{{=T.M("(**%.0d MB**)", total['bytes'] / 1048576)}}
|
||||
{{pass}}
|
||||
{{else:}}
|
||||
{{=T.M("**not available** (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
|
||||
{{=T.M("**not available** (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
|
||||
{{pass}}
|
||||
</p>
|
||||
<p>
|
||||
@@ -176,7 +176,7 @@
|
||||
{{=T.M("(**%.0d MB**)", ram['bytes'] / 10485576)}}
|
||||
{{pass}}
|
||||
{{else:}}
|
||||
{{=T.M("``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
|
||||
{{=T.M("``**not available**``:red (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
|
||||
{{pass}}
|
||||
</p>
|
||||
<p>
|
||||
@@ -205,7 +205,7 @@
|
||||
{{=T.M("(**%.0d MB**)", disk['bytes'] / 1048576)}}
|
||||
{{pass}}
|
||||
{{else:}}
|
||||
{{=T.M("``**not available**``:red (requires the Python [[guppy http://pypi.python.org/pypi/guppy/ popup]] library)")}}
|
||||
{{=T.M("``**not available**``:red (requires the Python [[Pympler https://pypi.python.org/pypi/Pympler popup]] library)")}}
|
||||
{{pass}}
|
||||
</p>
|
||||
<p>
|
||||
@@ -233,30 +233,22 @@
|
||||
<div class="clear"></div>
|
||||
{{pass}}
|
||||
|
||||
{{if request.function=='graph_model':}}
|
||||
{{if request.function=='d3_graph_model':}}
|
||||
<h2>{{=T("Graph Model")}}</h2>
|
||||
{{if not pgv:}}
|
||||
{{=T('pygraphviz library not found')}}
|
||||
{{elif not databases:}}
|
||||
{{if not databases:}}
|
||||
{{=T("No databases in this application")}}
|
||||
{{else:}}
|
||||
<div class="btn-group">
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="icon-download"></i> {{=T('Save model as...')}}
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
{{=IMG(_src=URL('appadmin', 'bg_graph_model'))}}
|
||||
{{else:}}
|
||||
<div id="vis"></div>
|
||||
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||
<script>
|
||||
// Define the d3 input data
|
||||
{{from gluon.serializers import json }}
|
||||
var nodes = {{=XML(json(nodes))}};
|
||||
var links = {{=XML(json(links))}};
|
||||
d3_graph();
|
||||
</script>
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
{{pass}}
|
||||
|
||||
{{if request.function == 'manage':}}
|
||||
<h2>{{=heading}}</h2>
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
{{left_sidebar_enabled,right_sidebar_enabled=False,('message' in globals())}}
|
||||
{{extend 'layout.html'}}
|
||||
|
||||
{{block header}}
|
||||
<header class="container-fluid background">
|
||||
<div class="jumbotron text-center">
|
||||
{{if response.title:}}
|
||||
<h1>{{=response.title}}
|
||||
<small>{{=response.subtitle or ''}}</small></h1>
|
||||
{{pass}}
|
||||
</div>
|
||||
</header>
|
||||
<center style="background-color: #333; color:white; padding:30px">
|
||||
<h1>/{{=request.application}}/{{=request.controller}}/{{=request.function}}
|
||||
</center>
|
||||
{{end}}
|
||||
|
||||
{{if 'message' in globals():}}
|
||||
@@ -27,26 +21,23 @@
|
||||
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li>
|
||||
<li>{{=T('You can modify this application and adapt it to your needs')}}</li>
|
||||
</ol>
|
||||
<center style="padding:50px">
|
||||
<a class="btn btn-primary" href="{{=URL('admin','default','index')}}">
|
||||
<i class="fa fa-cog"></i>
|
||||
{{=T("admin")}}
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="{{=URL('examples','default','index')}}">{{=T("Online examples")}}</a>
|
||||
<a class="btn btn-secondary" href="http://web2py.com">web2py.com</a>
|
||||
<a class="btn btn-secondary" href="http://web2py.com/book">{{=T('Documentation')}}</a>
|
||||
<a class="btn btn-secondary" href="{{=URL('default','api_get_user_email')}}">{{=T('API Example')}}</a>
|
||||
<a class="btn btn-secondary" href="{{=URL('default','grid/auth_user')}}">{{=T('Grid Example')}}</a>
|
||||
<a class="btn btn-secondary" href="{{=URL('default','wiki')}}">{{=T('Wiki Example')}}</a>
|
||||
</center>
|
||||
{{elif 'content' in globals():}}
|
||||
{{=content}}
|
||||
{{else:}}
|
||||
{{=BEAUTIFY(response._vars)}}
|
||||
{{pass}}
|
||||
|
||||
{{block right_sidebar}}
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title"><a class="btn-block"
|
||||
href="{{=URL('admin','default','index')}}">
|
||||
<i class="glyphicon glyphicon-cog"></i>
|
||||
{{=T("admin")}}
|
||||
</a></h3></div>
|
||||
<div class="panel-body">
|
||||
{{=T("Don't know what to do?")}}
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">{{=A(T("Online examples"), _href=URL('examples','default','index'))}}</li>
|
||||
<li class="list-group-item"><a href="http://web2py.com">web2py.com</a></li>
|
||||
<li class="list-group-item"><a href="http://web2py.com/book">{{=T('Documentation')}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
{{
|
||||
if request.args(0)=='login':
|
||||
if not 'register' in auth.settings.actions_disabled:
|
||||
form.add_button(T('Sign Up'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn btn-default')
|
||||
form.add_button(T('Sign Up'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn btn-default btn-secondary')
|
||||
pass
|
||||
if not 'request_reset_password' in auth.settings.actions_disabled:
|
||||
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn btn-default')
|
||||
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn btn-default btn-secondary')
|
||||
pass
|
||||
pass
|
||||
=form
|
||||
|
||||
@@ -7,10 +7,7 @@ It is used as default when a view is not provided for your controllers
|
||||
"""}}
|
||||
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
|
||||
{{if len(response._vars)==1:}}
|
||||
{{=BEAUTIFY(response._vars.values()[0])}}
|
||||
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
|
||||
{{elif len(response._vars)>1:}}
|
||||
{{=BEAUTIFY(response._vars)}}
|
||||
{{pass}}
|
||||
{{if request.is_local:}}
|
||||
{{=response.toolbar()}}
|
||||
{{pass}}
|
||||
|
||||
@@ -27,4 +27,4 @@ Notice:
|
||||
- no need to return a string
|
||||
even if the function is called via ajax.
|
||||
|
||||
'''}}{{if len(response._vars)==1:}}{{=response._vars.values()[0]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
|
||||
'''}}{{if len(response._vars)==1:}}{{=response._vars[next(iter(response._vars))]}}{{else:}}{{=BEAUTIFY(response._vars)}}{{pass}}
|
||||
@@ -20,85 +20,83 @@
|
||||
http://google.com/webmasters -->
|
||||
<meta name="google-site-verification" content="">
|
||||
<!-- include stylesheets -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>
|
||||
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
|
||||
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/>
|
||||
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap4.css')}}"/>
|
||||
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
|
||||
<!-- All JavaScript at the bottom, except for Modernizr which enables
|
||||
HTML5 elements & feature detects -->
|
||||
<script src="{{=URL('static','js/modernizr-2.8.3.min.js')}}"></script>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="{{=URL('static','js/respond-1.4.2.min.js')}}"></script>
|
||||
<![endif]-->
|
||||
<!-- Favicons -->
|
||||
{{include 'web2py_ajax.html'}} <!-- this includes jquery.js, calendar.js/.css and web2py.js -->
|
||||
{{block head}}{{end}}
|
||||
{{
|
||||
# using sidebars need to know what sidebar you want to use
|
||||
mc0 = 'col-md-12'
|
||||
mc1 = 'col-md-9'
|
||||
mc2 = 'col-md-6'
|
||||
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
|
||||
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
|
||||
middle_column = {0: mc0, 1: mc1, 2: mc2}[
|
||||
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
|
||||
}}
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
|
||||
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
|
||||
<!-- Navbar ======================================= -->
|
||||
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
{{=response.logo or ''}}
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}}
|
||||
</ul>
|
||||
{{if response.menu:}}
|
||||
{{=MENU(response.menu, _class='nav navbar-nav',li_class='dropdown',ul_class='dropdown-menu')}}
|
||||
<nav class="navbar navbar-toggleable-md navbar-light bg-faded">
|
||||
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="http://web2py.com">web2py</a>
|
||||
<div id="navbarNavDropdown" class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav">
|
||||
{{for _item in response.menu or []:}}
|
||||
{{if len(_item)<4 or not _item[3]:}}
|
||||
<li class="nav-item {{if _item[1]:}}active{{pass}}">
|
||||
<a class="nav-link" href="{{=_item[2]}}">{{=_item[0]}}</a>
|
||||
</li>
|
||||
{{else:}}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="{{=_item[2]}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{=_item[0]}}</a>
|
||||
<div class="dropdown-menu">
|
||||
{{for _subitem in _item[3]:}}
|
||||
<a class="dropdown-item" href="{{=_subitem[2]}}">{{=_subitem[0]}}</a>
|
||||
{{pass}}
|
||||
</div>
|
||||
</li>
|
||||
{{pass}}
|
||||
</div>
|
||||
{{pass}}
|
||||
</ul>
|
||||
<form class="form-inline my-2 my-lg-0">
|
||||
<input class="form-control mr-sm-2" type="text" placeholder="Search">
|
||||
</form>
|
||||
{{if 'auth' in globals():}}
|
||||
<ul class="navbar-nav navbar-right">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{if auth.user:}}{{=auth.user.first_name}}{{else:}}LOGIN{{pass}}
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
{{if auth.user:}}
|
||||
<a class="dropdown-item" href="{{=URL('default','user/profile')}}">{{=T('Profile')}}</a>
|
||||
<a class="dropdown-item" href="{{=URL('default','user/change_password')}}">{{=T('Change Password')}}</a>
|
||||
<a class="dropdown-item" href="{{=URL('default','user/logout')}}">{{=T('Logout')}}</a>
|
||||
{{else:}}
|
||||
<a class="dropdown-item" href="{{=URL('default','user/login')}}">{{=T('Login')}}</a>
|
||||
<a class="dropdown-item" href="{{=URL('default','user/register')}}">{{=T('Sign up')}}</a>
|
||||
<a class="dropdown-item" href="{{=URL('default','user/retrieve_password')}}">{{=T('Lost Password')}}</a>
|
||||
{{pass}}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{{pass}}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Masthead ===================================== -->
|
||||
{{block header}}
|
||||
{{end}}
|
||||
<!-- Main ========================================= -->
|
||||
<!-- Begin page content -->
|
||||
<div class="container-fluid main-container">
|
||||
{{if left_sidebar_enabled:}}
|
||||
<div class="col-md-3 left-sidebar">
|
||||
{{block left_sidebar}}
|
||||
<h3>Left Sidebar</h3>
|
||||
<p></p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{pass}}
|
||||
|
||||
<div class="{{=middle_column}}">
|
||||
<div class="col-md-12">
|
||||
{{block center}}
|
||||
{{include}}
|
||||
{{end}}
|
||||
{{=response.toolbar() if response.show_toolbar else ''}}
|
||||
</div>
|
||||
|
||||
{{if right_sidebar_enabled:}}
|
||||
<div class="col-md-3">
|
||||
{{block right_sidebar}}
|
||||
<h3>Right Sidebar</h3>
|
||||
<p></p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{pass}}
|
||||
|
||||
</div>
|
||||
|
||||
{{block footer}} <!-- this is default footer -->
|
||||
@@ -114,7 +112,7 @@
|
||||
{{end}}
|
||||
<!-- The javascript =============================== -->
|
||||
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
|
||||
<script src="{{=URL('static','js/web2py-bootstrap3.js')}}"></script>
|
||||
<script src="{{=URL('static','js/web2py-bootstrap4.js')}}"></script>
|
||||
{{block page_js}}{{end page_js}}
|
||||
{{if response.google_analytics_id:}}
|
||||
<!-- Analytics ==================================== -->
|
||||
@@ -125,7 +123,5 @@
|
||||
});
|
||||
</script>
|
||||
{{pass}}
|
||||
<!-- Share ============================y============ -->
|
||||
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,6 +10,10 @@ environment:
|
||||
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
- PYTHON: "C:/Python36"
|
||||
COVERAGE_PROCESS_START: gluon/tests/coverage.ini
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
clone_depth: 50
|
||||
|
||||
init:
|
||||
@@ -20,7 +24,6 @@ install:
|
||||
- python -m ensurepip
|
||||
- pip install codecov
|
||||
- git submodule update --init --recursive
|
||||
- pip install pycrypto
|
||||
# Check that we have the expected version and architecture for Python
|
||||
- "python --version"
|
||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
|
||||
@@ -26,7 +26,7 @@ if PY2:
|
||||
from email.MIMEText import MIMEText
|
||||
from email.Charset import add_charset, QP as charset_QP
|
||||
from urllib import FancyURLopener, urlencode, urlopen
|
||||
from urllib import quote as urllib_quote, unquote as urllib_unquote
|
||||
from urllib import quote as urllib_quote, unquote as urllib_unquote, quote_plus as urllib_quote_plus
|
||||
from string import maketrans
|
||||
from types import ClassType
|
||||
import cgi
|
||||
@@ -35,6 +35,7 @@ if PY2:
|
||||
from gluon.contrib import ipaddress
|
||||
BytesIO = StringIO
|
||||
reduce = reduce
|
||||
reload = reload
|
||||
hashlib_md5 = hashlib.md5
|
||||
iterkeys = lambda d: d.iterkeys()
|
||||
itervalues = lambda d: d.itervalues()
|
||||
@@ -63,7 +64,7 @@ if PY2:
|
||||
return None
|
||||
if isinstance(obj, (bytes, bytearray, buffer)):
|
||||
return bytes(obj)
|
||||
if isinstance(obj, unicode):
|
||||
if hasattr(obj, 'encode'):
|
||||
return obj.encode(charset, errors)
|
||||
raise TypeError('Expected bytes')
|
||||
|
||||
@@ -77,6 +78,7 @@ else:
|
||||
import pickle
|
||||
from io import StringIO, BytesIO
|
||||
import copyreg
|
||||
from importlib import reload
|
||||
from functools import reduce
|
||||
from html.parser import HTMLParser
|
||||
from http import cookies as Cookie
|
||||
@@ -94,7 +96,7 @@ else:
|
||||
from email.header import Header
|
||||
from email.charset import Charset, add_charset, QP as charset_QP
|
||||
from urllib.request import FancyURLopener, urlopen
|
||||
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode
|
||||
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode, quote_plus as urllib_quote_plus
|
||||
from http import cookiejar as cookielib
|
||||
from xmlrpc.client import ProtocolError
|
||||
import html # warning, this is the python3 module and not the web2py html module
|
||||
@@ -122,7 +124,7 @@ else:
|
||||
return None
|
||||
if isinstance(obj, (bytes, bytearray, memoryview)):
|
||||
return bytes(obj)
|
||||
if isinstance(obj, str):
|
||||
if hasattr(obj, 'encode'):
|
||||
return obj.encode(charset, errors)
|
||||
raise TypeError('Expected bytes')
|
||||
|
||||
@@ -151,7 +153,7 @@ def with_metaclass(meta, *bases):
|
||||
def to_unicode(obj, charset='utf-8', errors='strict'):
|
||||
if obj is None:
|
||||
return None
|
||||
if not isinstance(obj, bytes):
|
||||
if not hasattr(obj, 'decode'):
|
||||
return text_type(obj)
|
||||
return obj.decode(charset, errors)
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ def app_pack(app, request, raise_ex=False, filenames=None):
|
||||
|
||||
"""
|
||||
try:
|
||||
if filenames is None: app_cleanup(app, request)
|
||||
if filenames is None:
|
||||
app_cleanup(app, request)
|
||||
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
|
||||
w2p_pack(filename, apath(app, request), filenames=filenames)
|
||||
return filename
|
||||
@@ -104,7 +105,8 @@ def app_cleanup(app, request):
|
||||
if os.path.exists(path):
|
||||
for f in os.listdir(path):
|
||||
try:
|
||||
if f[:1] != '.': os.unlink(os.path.join(path, f))
|
||||
if f[:1] != '.':
|
||||
os.unlink(os.path.join(path, f))
|
||||
except IOError:
|
||||
r = False
|
||||
|
||||
@@ -113,7 +115,8 @@ def app_cleanup(app, request):
|
||||
if os.path.exists(path):
|
||||
for f in os.listdir(path):
|
||||
try:
|
||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
||||
if f[:1] != '.':
|
||||
recursive_unlink(os.path.join(path, f))
|
||||
except (OSError, IOError):
|
||||
r = False
|
||||
|
||||
@@ -123,7 +126,8 @@ def app_cleanup(app, request):
|
||||
CacheOnDisk(folder=path).clear()
|
||||
for f in os.listdir(path):
|
||||
try:
|
||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
||||
if f[:1] != '.':
|
||||
recursive_unlink(os.path.join(path, f))
|
||||
except (OSError, IOError):
|
||||
r = False
|
||||
return r
|
||||
@@ -175,10 +179,9 @@ def app_create(app, request, force=False, key=None, info=False):
|
||||
return False
|
||||
try:
|
||||
w2p_unpack('welcome.w2p', path)
|
||||
for subfolder in [
|
||||
'models', 'views', 'controllers', 'databases',
|
||||
'modules', 'cron', 'errors', 'sessions', 'cache',
|
||||
'languages', 'static', 'private', 'uploads']:
|
||||
for subfolder in ['models', 'views', 'controllers', 'databases',
|
||||
'modules', 'cron', 'errors', 'sessions', 'cache',
|
||||
'languages', 'static', 'private', 'uploads']:
|
||||
subpath = os.path.join(path, subfolder)
|
||||
if not os.path.exists(subpath):
|
||||
os.mkdir(subpath)
|
||||
@@ -368,7 +371,7 @@ def unzip(filename, dir, subfolder=''):
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.startswith(subfolder):
|
||||
continue
|
||||
#print name[n:]
|
||||
# print name[n:]
|
||||
if name.endswith('/'):
|
||||
folder = os.path.join(dir, name[n:])
|
||||
if not os.path.exists(folder):
|
||||
@@ -435,6 +438,7 @@ def add_path_first(path):
|
||||
if not global_settings.web2py_runtime_gae:
|
||||
site.addsitedir(path)
|
||||
|
||||
|
||||
def try_mkdir(path):
|
||||
if not os.path.exists(path):
|
||||
try:
|
||||
@@ -444,11 +448,12 @@ def try_mkdir(path):
|
||||
else:
|
||||
os.mkdir(path)
|
||||
except OSError as e:
|
||||
if e.strerror == 'File exists': # In case of race condition.
|
||||
if e.strerror == 'File exists': # In case of race condition.
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def create_missing_folders():
|
||||
if not global_settings.web2py_runtime_gae:
|
||||
for path in ('applications', 'deposit', 'site-packages', 'logs'):
|
||||
|
||||
1055
gluon/authapi.py
Normal file
1055
gluon/authapi.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ import fnmatch
|
||||
import os
|
||||
import copy
|
||||
import random
|
||||
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long
|
||||
from gluon._compat import builtin, PY2, unicodeT, to_native, to_bytes, iteritems, basestring, reduce, xrange, long, reload
|
||||
from gluon.storage import Storage, List
|
||||
from gluon.template import parse_template
|
||||
from gluon.restricted import restricted, compile2
|
||||
@@ -41,11 +41,12 @@ import imp
|
||||
import logging
|
||||
import types
|
||||
from functools import reduce
|
||||
logger = logging.getLogger("web2py")
|
||||
from gluon import rewrite
|
||||
from gluon.custom_import import custom_import_install
|
||||
import py_compile
|
||||
|
||||
logger = logging.getLogger("web2py")
|
||||
|
||||
is_pypy = settings.global_settings.is_pypy
|
||||
is_gae = settings.global_settings.web2py_runtime_gae
|
||||
is_jython = settings.global_settings.is_jython
|
||||
@@ -111,7 +112,7 @@ class mybuiltin(object):
|
||||
NOTE could simple use a dict and populate it,
|
||||
NOTE not sure if this changes things though if monkey patching import.....
|
||||
"""
|
||||
#__builtins__
|
||||
# __builtins__
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return getattr(builtin, key)
|
||||
@@ -185,7 +186,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
||||
else:
|
||||
statement = "$.web2py.component('%s','%s');" % (url, target)
|
||||
attr['_data-w2p_remote'] = url
|
||||
if not target is None:
|
||||
if target is not None:
|
||||
return DIV(content, **attr)
|
||||
|
||||
else:
|
||||
@@ -204,14 +205,15 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
||||
other_response = Response()
|
||||
other_request.env.path_info = '/' + \
|
||||
'/'.join([request.application, c, f] +
|
||||
map(str, other_request.args))
|
||||
[str(a) for a in other_request.args])
|
||||
other_request.env.query_string = \
|
||||
vars and URL(vars=vars).split('?')[1] or ''
|
||||
other_request.env.http_web2py_component_location = \
|
||||
request.env.path_info
|
||||
other_request.cid = target
|
||||
other_request.env.http_web2py_component_element = target
|
||||
other_request.restful = types.MethodType(request.restful.__func__, other_request) # A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
||||
other_request.restful = types.MethodType(request.restful.__func__, other_request)
|
||||
# A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
||||
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
||||
|
||||
other_environment = copy.copy(current.globalenv) # NASTY
|
||||
@@ -286,7 +288,7 @@ class LoadFactory(object):
|
||||
other_response = globals.Response()
|
||||
other_request.env.path_info = '/' + \
|
||||
'/'.join([request.application, c, f] +
|
||||
map(str, other_request.args))
|
||||
[str(a) for a in other_request.args])
|
||||
other_request.env.query_string = \
|
||||
vars and html.URL(vars=vars).split('?')[1] or ''
|
||||
other_request.env.http_web2py_component_location = \
|
||||
@@ -405,7 +407,7 @@ def build_environment(request, response, session, store_current=True):
|
||||
"""
|
||||
Build the environment dictionary into which web2py files are executed.
|
||||
"""
|
||||
#h,v = html,validators
|
||||
# h,v = html,validators
|
||||
environment = dict(_base_environment_)
|
||||
|
||||
if not request.env:
|
||||
@@ -418,7 +420,7 @@ def build_environment(request, response, session, store_current=True):
|
||||
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
|
||||
]
|
||||
|
||||
t = environment['T'] = translator(os.path.join(request.folder,'languages'),
|
||||
t = environment['T'] = translator(os.path.join(request.folder, 'languages'),
|
||||
request.env.http_accept_language)
|
||||
c = environment['cache'] = Cache(request)
|
||||
|
||||
@@ -506,10 +508,12 @@ def compile_models(folder):
|
||||
save_pyc(filename)
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def find_exposed_functions(data):
|
||||
data = regex_longcomments.sub('',data)
|
||||
data = regex_longcomments.sub('', data)
|
||||
return regex_expose.findall(data)
|
||||
|
||||
|
||||
def compile_controllers(folder):
|
||||
"""
|
||||
Compiles all the controllers in the application specified by `folder`
|
||||
@@ -524,16 +528,19 @@ def compile_controllers(folder):
|
||||
command = data + "\nresponse._vars=response._caller(%s)\n" % \
|
||||
function
|
||||
filename = pjoin(folder, 'compiled',
|
||||
'controllers.%s.%s.py' % (fname[:-3],function))
|
||||
'controllers.%s.%s.py' % (fname[:-3], function))
|
||||
write_file(filename, command)
|
||||
save_pyc(filename)
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def model_cmp(a, b, sep='.'):
|
||||
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
|
||||
|
||||
|
||||
def model_cmp_sep(a, b, sep=os.path.sep):
|
||||
return model_cmp(a,b,sep)
|
||||
return model_cmp(a, b, sep)
|
||||
|
||||
|
||||
def run_models_in(environment):
|
||||
"""
|
||||
@@ -544,7 +551,7 @@ def run_models_in(environment):
|
||||
request = current.request
|
||||
folder = request.folder
|
||||
c = request.controller
|
||||
#f = environment['request'].function
|
||||
# f = environment['request'].function
|
||||
response = current.response
|
||||
|
||||
path = pjoin(folder, 'models')
|
||||
@@ -557,9 +564,11 @@ def run_models_in(environment):
|
||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
|
||||
else:
|
||||
if compiled:
|
||||
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), key=lambda f: '{0:03d}'.format(f.count('.')) + f)
|
||||
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0),
|
||||
key=lambda f: '{0:03d}'.format(f.count('.')) + f)
|
||||
else:
|
||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
|
||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False),
|
||||
key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
|
||||
|
||||
models_to_run = None
|
||||
for model in models:
|
||||
@@ -570,10 +579,10 @@ def run_models_in(environment):
|
||||
if models_to_run:
|
||||
if compiled:
|
||||
n = len(cpath)+8
|
||||
fname = model[n:-4].replace('.','/')+'.py'
|
||||
fname = model[n:-4].replace('.', '/')+'.py'
|
||||
else:
|
||||
n = len(path)+1
|
||||
fname = model[n:].replace(os.path.sep,'/')
|
||||
fname = model[n:].replace(os.path.sep, '/')
|
||||
if not regex.search(fname) and c != 'appadmin':
|
||||
continue
|
||||
elif compiled:
|
||||
@@ -583,6 +592,7 @@ def run_models_in(environment):
|
||||
ccode = getcfs(model, model, f)
|
||||
restricted(ccode, environment, layer=model)
|
||||
|
||||
|
||||
def run_controller_in(controller, function, environment):
|
||||
"""
|
||||
Runs the controller.function() (for the app specified by
|
||||
@@ -596,13 +606,13 @@ def run_controller_in(controller, function, environment):
|
||||
badc = 'invalid controller (%s/%s)' % (controller, function)
|
||||
badf = 'invalid function (%s/%s)' % (controller, function)
|
||||
if os.path.exists(cpath):
|
||||
filename = pjoin(cpath, 'controllers.%s.%s.pyc'
|
||||
% (controller, function))
|
||||
if not os.path.exists(filename):
|
||||
filename = pjoin(cpath, 'controllers.%s.%s.pyc' % (controller, function))
|
||||
try:
|
||||
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
|
||||
except IOError:
|
||||
raise HTTP(404,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||
web2py_error=badf)
|
||||
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
|
||||
elif function == '_TEST':
|
||||
# TESTING: adjust the path to include site packages
|
||||
from gluon.settings import global_settings
|
||||
@@ -623,21 +633,21 @@ def run_controller_in(controller, function, environment):
|
||||
code += TEST_CODE
|
||||
ccode = compile2(code, filename)
|
||||
else:
|
||||
filename = pjoin(folder, 'controllers/%s.py'
|
||||
% controller)
|
||||
if not os.path.exists(filename):
|
||||
filename = pjoin(folder, 'controllers/%s.py' % controller)
|
||||
try:
|
||||
code = getcfs(filename, filename, lambda: read_file(filename))
|
||||
except IOError:
|
||||
raise HTTP(404,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
||||
web2py_error=badc)
|
||||
code = getcfs(filename, filename, lambda: read_file(filename))
|
||||
exposed = find_exposed_functions(code)
|
||||
if not function in exposed:
|
||||
if function not in exposed:
|
||||
raise HTTP(404,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||
web2py_error=badf)
|
||||
code = "%s\nresponse._vars=response._caller(%s)" % (code, function)
|
||||
layer = "%s:%s" % (filename, function)
|
||||
ccode = getcfs(layer, filename, lambda: compile2(code, layer))
|
||||
ccode = getcfs(layer, filename, lambda: compile2(code, filename))
|
||||
|
||||
restricted(ccode, environment, layer=filename)
|
||||
response = environment["response"]
|
||||
@@ -666,8 +676,9 @@ def run_view_in(environment):
|
||||
badv = 'invalid view (%s)' % view
|
||||
patterns = response.get('generic_patterns')
|
||||
layer = None
|
||||
scode = None
|
||||
if patterns:
|
||||
regex = re_compile('|'.join(map(fnmatch.translate, patterns)))
|
||||
regex = re_compile('|'.join(fnmatch.translate(p) for p in patterns))
|
||||
short_action = '%(controller)s/%(function)s.%(extension)s' % request
|
||||
allow_generic = regex.search(short_action)
|
||||
else:
|
||||
@@ -678,7 +689,7 @@ def run_view_in(environment):
|
||||
layer = 'file stream'
|
||||
else:
|
||||
filename = pjoin(folder, 'views', view)
|
||||
if os.path.exists(cpath): # compiled views
|
||||
if os.path.exists(cpath): # compiled views
|
||||
x = view.replace('/', '.')
|
||||
files = ['views.%s.pyc' % x]
|
||||
is_compiled = os.path.exists(pjoin(cpath, files[0]))
|
||||
@@ -698,23 +709,27 @@ def run_view_in(environment):
|
||||
ccode = getcfs(compiled, compiled, lambda: read_pyc(compiled))
|
||||
layer = compiled
|
||||
break
|
||||
if not os.path.exists(filename) and allow_generic:
|
||||
view = 'generic.' + request.extension
|
||||
filename = pjoin(folder, 'views', view)
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
||||
web2py_error=badv)
|
||||
layer = filename
|
||||
# Compile the template
|
||||
ccode = parse_template(view,
|
||||
pjoin(folder, 'views'),
|
||||
context=environment)
|
||||
|
||||
restricted(ccode, environment, layer=layer)
|
||||
# if the view is not compiled
|
||||
if not layer:
|
||||
if not os.path.exists(filename) and allow_generic:
|
||||
view = 'generic.' + request.extension
|
||||
filename = pjoin(folder, 'views', view)
|
||||
if not os.path.exists(filename):
|
||||
raise HTTP(404,
|
||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
||||
web2py_error=badv)
|
||||
# Parse template
|
||||
scode = parse_template(view,
|
||||
pjoin(folder, 'views'),
|
||||
context=environment)
|
||||
# Compile template
|
||||
ccode = compile2(scode, filename)
|
||||
layer = filename
|
||||
restricted(ccode, environment, layer=layer, scode=scode)
|
||||
# parse_template saves everything in response body
|
||||
return environment['response'].body.getvalue()
|
||||
|
||||
|
||||
def remove_compiled_application(folder):
|
||||
"""
|
||||
Deletes the folder `compiled` containing the compiled application.
|
||||
|
||||
@@ -330,7 +330,7 @@ CONTENT_TYPE = {
|
||||
'.lha': 'application/x-lha',
|
||||
'.lhs': 'text/x-literate-haskell',
|
||||
'.lhz': 'application/x-lhz',
|
||||
'.load' : 'text/html',
|
||||
'.load': 'text/html',
|
||||
'.log': 'text/x-log',
|
||||
'.lrz': 'application/x-lrzip',
|
||||
'.ltx': 'text/x-tex',
|
||||
@@ -823,7 +823,7 @@ CONTENT_TYPE = {
|
||||
'.xsd': 'application/xml',
|
||||
'.xsl': 'application/xslt+xml',
|
||||
'.xslfo': 'text/x-xslfo',
|
||||
'.xslm' : 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||
'.xslm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||
'.xslt': 'application/xslt+xml',
|
||||
'.xspf': 'application/xspf+xml',
|
||||
'.xul': 'application/vnd.mozilla.xul+xml',
|
||||
@@ -843,7 +843,7 @@ def contenttype(filename, default='text/plain'):
|
||||
"""
|
||||
Returns the Content-Type string matching extension of the given filename.
|
||||
"""
|
||||
filename=to_native(filename)
|
||||
filename = to_native(filename)
|
||||
i = filename.rfind('.')
|
||||
if i >= 0:
|
||||
default = CONTENT_TYPE.get(filename[i:].lower(), default)
|
||||
|
||||
@@ -7,15 +7,13 @@ db = get_db()
|
||||
"""
|
||||
import os
|
||||
from gluon import *
|
||||
from pydal.adapters import ADAPTERS, PostgreSQLAdapter
|
||||
from pydal.helpers.classes import UseDatabaseStoredFile
|
||||
from pydal.adapters import adapters, PostgrePsyco
|
||||
from pydal.helpers.classes import DatabaseStoredFile
|
||||
|
||||
class HerokuPostgresAdapter(UseDatabaseStoredFile,PostgreSQLAdapter):
|
||||
drivers = ('psycopg2',)
|
||||
@adapters.register_for('postgres')
|
||||
class HerokuPostgresAdapter(DatabaseStoredFile, PostgrePsyco):
|
||||
uploads_in_blob = True
|
||||
|
||||
ADAPTERS['postgres'] = HerokuPostgresAdapter
|
||||
|
||||
def get_db(name = None, pool_size=10):
|
||||
if not name:
|
||||
names = [n for n in os.environ.keys()
|
||||
|
||||
@@ -45,7 +45,7 @@ class RESIZE(object):
|
||||
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
|
||||
background.paste(
|
||||
img,
|
||||
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
|
||||
((self.nx - img.size[0]) // 2, (self.ny - img.size[1]) // 2))
|
||||
background.save(s, 'JPEG', quality=self.quality)
|
||||
else:
|
||||
img.save(s, 'JPEG', quality=self.quality)
|
||||
|
||||
@@ -49,7 +49,8 @@ class CasAuth(object):
|
||||
email=lambda v: v.get('email', None),
|
||||
user_id=lambda v: v['user']),
|
||||
casversion=1,
|
||||
casusername='cas:user'
|
||||
casusername='cas:user',
|
||||
change_password_url=None
|
||||
):
|
||||
self.urlbase = urlbase
|
||||
self.cas_login_url = "%s/%s" % (self.urlbase, actions[0])
|
||||
@@ -64,6 +65,9 @@ class CasAuth(object):
|
||||
#vars=current.request.vars,
|
||||
scheme=True)
|
||||
|
||||
# URL to let users change their password in the IDP system
|
||||
self.cas_change_password_url = change_password_url
|
||||
|
||||
def login_url(self, next="/"):
|
||||
current.session.token = self._CAS_login()
|
||||
return next
|
||||
@@ -74,6 +78,10 @@ class CasAuth(object):
|
||||
self._CAS_logout()
|
||||
return next
|
||||
|
||||
def change_password_url(self, next="/"):
|
||||
self._CAS_change_password()
|
||||
return next
|
||||
|
||||
def get_user(self):
|
||||
user = current.session.token
|
||||
if user:
|
||||
@@ -135,3 +143,6 @@ class CasAuth(object):
|
||||
redirects to the CAS logout page
|
||||
"""
|
||||
redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url))
|
||||
|
||||
def _CAS_change_password(self):
|
||||
redirect(self.cas_change_password_url)
|
||||
|
||||
@@ -78,10 +78,13 @@ class RPXAccount(object):
|
||||
|
||||
def get_user(self):
|
||||
request = self.request
|
||||
if request.vars.token:
|
||||
# Janrain now sends the token via both a POST body and the query
|
||||
# string, so we should keep only one of these.
|
||||
token = request.post_vars.token or request.get_vars.token
|
||||
if token:
|
||||
user = Storage()
|
||||
data = urllib.urlencode(
|
||||
dict(apiKey=self.api_key, token=request.vars.token))
|
||||
dict(apiKey=self.api_key, token=token))
|
||||
auth_info_json = fetch(self.auth_url + '?' + data)
|
||||
auth_info = json.loads(auth_info_json)
|
||||
|
||||
|
||||
@@ -506,9 +506,9 @@ def ldap_auth(server='ldap',
|
||||
l = []
|
||||
for group in ldap_groups_of_the_user:
|
||||
if group in group_mapping:
|
||||
l += group_mapping[group]
|
||||
l.append(group_mapping[group])
|
||||
ldap_groups_of_the_user = l
|
||||
logging.info("User groups after remapping: %s" % str(l))
|
||||
logger.info("User groups after remapping: %s" % str(l))
|
||||
|
||||
#
|
||||
# Get all group name where the user is in actually in local db
|
||||
@@ -528,7 +528,7 @@ def ldap_auth(server='ldap',
|
||||
except AttributeError as e:
|
||||
db_user_id = db.auth_user.insert(email=username, first_name=username)
|
||||
if not db_user_id:
|
||||
logging.error(
|
||||
logger.error(
|
||||
'There is no username or email for %s!' % username)
|
||||
raise
|
||||
# if old pydal version, assume this is a relational database which can do joins
|
||||
@@ -550,7 +550,7 @@ def ldap_auth(server='ldap',
|
||||
for group in db_group_search.select(db.auth_group.id, db.auth_group.role, distinct=True):
|
||||
db_group_id[group.role] = group.id
|
||||
db_groups_of_the_user.append(group.role)
|
||||
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
|
||||
logger.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
|
||||
|
||||
auth_membership_changed = False
|
||||
#
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import urllib
|
||||
from gluon import *
|
||||
from gluon.tools import fetch
|
||||
from gluon.storage import Storage
|
||||
import json
|
||||
|
||||
from gluon._compat import urlencode
|
||||
|
||||
class RPXAccount(object):
|
||||
|
||||
@@ -78,10 +77,13 @@ class RPXAccount(object):
|
||||
|
||||
def get_user(self):
|
||||
request = self.request
|
||||
if request.vars.token:
|
||||
# Janrain now sends the token via both a POST body and the query
|
||||
# string, so we should keep only one of these.
|
||||
token = request.post_vars.token or request.get_vars.token
|
||||
if token:
|
||||
user = Storage()
|
||||
data = urllib.urlencode(
|
||||
dict(apiKey=self.api_key, token=request.vars.token))
|
||||
data = urlencode(
|
||||
dict(apiKey=self.api_key, token=token))
|
||||
auth_info_json = fetch(self.auth_url + '?' + data)
|
||||
auth_info = json.loads(auth_info_json)
|
||||
|
||||
|
||||
@@ -104,11 +104,12 @@ def obj2dict(obj, processed=None):
|
||||
types.BuiltinFunctionType,
|
||||
types.BuiltinMethodType))
|
||||
|
||||
def saml2_handler(session, request, config_filename = None):
|
||||
def saml2_handler(session, request, config_filename = None, entityid = None):
|
||||
config_filename = config_filename or os.path.join(request.folder,'private','sp_conf')
|
||||
client = Saml2Client(config_file = config_filename)
|
||||
idps = client.metadata.with_descriptor("idpsso")
|
||||
entityid = idps.keys()[0]
|
||||
if not entityid:
|
||||
idps = client.metadata.with_descriptor("idpsso")
|
||||
entityid = idps.keys()[0]
|
||||
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
|
||||
binding, destination = client.pick_binding(
|
||||
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
|
||||
@@ -119,7 +120,7 @@ def saml2_handler(session, request, config_filename = None):
|
||||
if not request.vars.SAMLResponse:
|
||||
req_id, req = client.create_authn_request(destination, binding=binding)
|
||||
relay_state = web2py_uuid().replace('-','')
|
||||
session.saml_outstanding_queries = {req_id: request.url}
|
||||
session.saml_outstanding_queries = {req_id: request.url}
|
||||
session.saml_req_id = req_id
|
||||
http_args = client.apply_binding(binding, str(req), destination,
|
||||
relay_state=relay_state)
|
||||
@@ -145,12 +146,21 @@ class Saml2Auth(object):
|
||||
username=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
||||
email=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
||||
user_id=lambda v:v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
||||
)):
|
||||
), logout_url=None, change_password_url=None, entityid=None):
|
||||
self.config_file = config_file
|
||||
self.maps = maps
|
||||
|
||||
# URL for redirecting users to when they sign out
|
||||
self.saml_logout_url = logout_url
|
||||
|
||||
# URL to let users change their password in the IDP system
|
||||
self.saml_change_password_url = change_password_url
|
||||
|
||||
# URL to specify an IDP if using federation metadata or an MDQ
|
||||
self.entityid = entityid
|
||||
|
||||
def login_url(self, next="/"):
|
||||
d = saml2_handler(current.session, current.request)
|
||||
d = saml2_handler(current.session, current.request, entityid=self.entityid)
|
||||
if 'url' in d:
|
||||
redirect(d['url'])
|
||||
elif 'error' in d:
|
||||
@@ -170,6 +180,12 @@ class Saml2Auth(object):
|
||||
|
||||
def logout_url(self, next="/"):
|
||||
current.session.saml2_info = None
|
||||
current.session.auth = None
|
||||
self._SAML_logout()
|
||||
return next
|
||||
|
||||
def change_password_url(self, next="/"):
|
||||
self._SAML_change_password()
|
||||
return next
|
||||
|
||||
def get_user(self):
|
||||
@@ -180,3 +196,13 @@ class Saml2Auth(object):
|
||||
d[key] = self.maps[key](user)
|
||||
return d
|
||||
return None
|
||||
|
||||
def _SAML_logout(self):
|
||||
"""
|
||||
exposed SAML.logout()
|
||||
redirects to the SAML logout page
|
||||
"""
|
||||
redirect(self.saml_logout_url)
|
||||
|
||||
def _SAML_change_password(self):
|
||||
redirect(self.saml_change_password_url)
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
# license MIT/BSD/GPL
|
||||
from __future__ import print_function
|
||||
import re
|
||||
import sys
|
||||
import urllib
|
||||
from gluon._compat import maketrans, urllib_quote, unicodeT, to_bytes, to_native, xrange
|
||||
from gluon.utils import local_html_escape as escape
|
||||
from ast import parse as ast_parse
|
||||
import ast
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
if PY2:
|
||||
from urllib import quote as urllib_quote
|
||||
from string import maketrans
|
||||
else:
|
||||
from urllib.parse import quote as urllib_quote
|
||||
maketrans = str.maketrans
|
||||
|
||||
|
||||
"""
|
||||
TODO: next version should use MathJax
|
||||
@@ -544,7 +551,7 @@ regex_code = re.compile(
|
||||
'(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',
|
||||
re.S)
|
||||
regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\s*]+)*)\*\*')
|
||||
regex_del = re.compile(r'~~(?P<t>[^\s*]+( +[^\s*]+)*)~~')
|
||||
regex_del = re.compile(r'~~(?P<t>[^\s~]+( +[^\s~]+)*)~~')
|
||||
regex_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
|
||||
regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
|
||||
regex_list = re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
|
||||
@@ -564,6 +571,29 @@ ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14
|
||||
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05', "'`:*~\\[]{}@$+-.#\n")
|
||||
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
|
||||
|
||||
def local_html_escape(data, quote=False):
|
||||
"""
|
||||
Works with bytes.
|
||||
Replace special characters "&", "<" and ">" to HTML-safe sequences.
|
||||
If the optional flag quote is true (the default), the quotation mark
|
||||
characters, both double quote (") and single quote (') characters are also
|
||||
translated.
|
||||
"""
|
||||
if PY2:
|
||||
import cgi
|
||||
data = cgi.escape(data, quote)
|
||||
return data.replace("'", "'") if quote else data
|
||||
else:
|
||||
import html
|
||||
if isinstance(data, str):
|
||||
return html.escape(data, quote=quote)
|
||||
data = data.replace(b"&", b"&") # Must be done first!
|
||||
data = data.replace(b"<", b"<")
|
||||
data = data.replace(b">", b">")
|
||||
if quote:
|
||||
data = data.replace(b'"', b""")
|
||||
data = data.replace(b'\'', b"'")
|
||||
return data
|
||||
|
||||
def make_dict(b):
|
||||
return '{%s}' % regex_quote.sub("'\g<name>':", b)
|
||||
@@ -579,7 +609,7 @@ def safe_eval(node_or_string, env):
|
||||
_safe_names = {'None': None, 'True': True, 'False': False}
|
||||
_safe_names.update(env)
|
||||
if isinstance(node_or_string, basestring):
|
||||
node_or_string = ast_parse(node_or_string, mode='eval')
|
||||
node_or_string = ast.parse(node_or_string, mode='eval')
|
||||
if isinstance(node_or_string, ast.Expression):
|
||||
node_or_string = node_or_string.body
|
||||
|
||||
@@ -599,14 +629,14 @@ def safe_eval(node_or_string, env):
|
||||
if node.id in _safe_names:
|
||||
return _safe_names[node.id]
|
||||
elif isinstance(node, ast.BinOp) and \
|
||||
isinstance(node.op, (Add, Sub)) and \
|
||||
isinstance(node.right, Num) and \
|
||||
isinstance(node.op, (ast.Add, ast.Sub)) and \
|
||||
isinstance(node.right, ast.Num) and \
|
||||
isinstance(node.right.n, complex) and \
|
||||
isinstance(node.left, Num) and \
|
||||
isinstance(node.left, ast.Num) and \
|
||||
isinstance(node.left.n, (int, long, float)):
|
||||
left = node.left.n
|
||||
right = node.right.n
|
||||
if isinstance(node.op, Add):
|
||||
if isinstance(node.op, ast.Add):
|
||||
return left + right
|
||||
else:
|
||||
return left - right
|
||||
@@ -765,7 +795,7 @@ def render(text,
|
||||
'<table><tbody><tr class="first"><td>a</td><td>b</td></tr><tr class="even"><td>c</td><td>d</td></tr></tbody></table>'
|
||||
|
||||
>>> render("----\\nhello world\\n----\\n")
|
||||
'<blockquote>hello world</blockquote>'
|
||||
'<blockquote><p>hello world</p></blockquote>'
|
||||
|
||||
>>> render('[[myanchor]]')
|
||||
'<p><span class="anchor" id="markmin_myanchor"></span></p>'
|
||||
@@ -946,7 +976,8 @@ def render(text,
|
||||
if protolinks == "default":
|
||||
protolinks = protolinks_simple
|
||||
pp = '\n' if pretty_print else ''
|
||||
text = to_native(text)
|
||||
text = text if text is None or isinstance(text, str) else text.decode('utf8', 'strict')
|
||||
|
||||
if not (isinstance(text, str)):
|
||||
text = str(text or '')
|
||||
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
|
||||
@@ -994,7 +1025,7 @@ def render(text,
|
||||
return LINK
|
||||
|
||||
text = regex_link.sub(mark_link, text)
|
||||
text = escape(text)
|
||||
text = local_html_escape(text)
|
||||
|
||||
if protolinks:
|
||||
text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text)
|
||||
@@ -1035,7 +1066,7 @@ def render(text,
|
||||
if pend and mtag == '.': # paragraph in a list:
|
||||
out.append(etags.pop())
|
||||
ltags.pop()
|
||||
for i in xrange(lent - lev):
|
||||
for i in range(lent - lev):
|
||||
out.append('<' + tag + '>' + pp)
|
||||
etags.append('</' + tag + '>' + pp)
|
||||
lev += 1
|
||||
@@ -1044,7 +1075,7 @@ def render(text,
|
||||
elif lent == lev:
|
||||
if tlev[-1] != tag:
|
||||
# type of list is changed (ul<=>ol):
|
||||
for i in xrange(ltags.count(lent)):
|
||||
for i in range(ltags.count(lent)):
|
||||
ltags.pop()
|
||||
out.append(etags.pop())
|
||||
tlev[-1] = tag
|
||||
@@ -1209,7 +1240,7 @@ def render(text,
|
||||
s = '<blockquote%s%s>%s</blockquote>%s' \
|
||||
% (t_cls,
|
||||
t_id,
|
||||
'\n'.join(strings[bq_begin:lineno]), pp)
|
||||
render('\n'.join(strings[bq_begin:lineno])), pp)
|
||||
mtag = 'q'
|
||||
else:
|
||||
s = '<hr />'
|
||||
@@ -1322,10 +1353,10 @@ def render(text,
|
||||
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
|
||||
if not k:
|
||||
return m.group(0)
|
||||
k = escape(k)
|
||||
k = local_html_escape(k)
|
||||
t = t or ''
|
||||
style = 'width:%s' % w if w else ''
|
||||
title = ' title="%s"' % escape(a).replace(META, DISABLED_META) if a else ''
|
||||
title = ' title="%s"' % local_html_escape(a).replace(META, DISABLED_META) if a else ''
|
||||
p_begin = p_end = ''
|
||||
if p == 'center':
|
||||
p_begin = '<p style="text-align:center">'
|
||||
@@ -1349,7 +1380,7 @@ def render(text,
|
||||
autolinks, protolinks, class_prefix, id_prefix, pretty_print)
|
||||
return '<%(p)s controls="controls"%(title)s%(style)s><source src="%(k)s" />%(t)s</%(p)s>' \
|
||||
% dict(p=p, title=title, style=style, k=k, t=t)
|
||||
alt = ' alt="%s"' % escape(t).replace(META, DISABLED_META) if t else ''
|
||||
alt = ' alt="%s"' % local_html_escape(t).replace(META, DISABLED_META) if t else ''
|
||||
return '%(begin)s<img src="%(k)s"%(alt)s%(title)s%(style)s />%(end)s' \
|
||||
% dict(begin=p_begin, k=k, alt=alt, title=title, style=style, end=p_end)
|
||||
|
||||
@@ -1358,12 +1389,12 @@ def render(text,
|
||||
if not k and not t:
|
||||
return m.group(0)
|
||||
t = t or ''
|
||||
a = escape(a) if a else ''
|
||||
a = local_html_escape(a) if a else ''
|
||||
if k:
|
||||
if '#' in k and ':' not in k.split('#')[0]:
|
||||
# wikipage, not external url
|
||||
k = k.replace('#', '#' + id_prefix)
|
||||
k = escape(k)
|
||||
k = local_html_escape(k)
|
||||
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
|
||||
target = ' target="_blank"' if p == 'popup' else ''
|
||||
t = render(t, {}, {}, 'br', URL, environment, latex, None,
|
||||
@@ -1373,7 +1404,7 @@ def render(text,
|
||||
if t == 'NEWLINE' and not a:
|
||||
return '<br />' + pp
|
||||
return '<span class="anchor" id="%s">%s</span>' % (
|
||||
escape(id_prefix + t),
|
||||
local_html_escape(id_prefix + t),
|
||||
render(a, {}, {}, 'br', URL,
|
||||
environment, latex, autolinks,
|
||||
protolinks, class_prefix,
|
||||
@@ -1399,7 +1430,7 @@ def render(text,
|
||||
def expand_meta(m):
|
||||
code, b, p, s = segments.pop(0)
|
||||
if code is None or m.group() == DISABLED_META:
|
||||
return escape(s)
|
||||
return local_html_escape(s)
|
||||
if b in extra:
|
||||
if code[:1] == '\n':
|
||||
code = code[1:]
|
||||
@@ -1411,7 +1442,7 @@ def render(text,
|
||||
return str(extra[b](code))
|
||||
elif b == 'cite':
|
||||
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
|
||||
(id_prefix + d, b, d) for d in escape(code).split(',')) + ']'
|
||||
(id_prefix + d, b, d) for d in local_html_escape(code).split(',')) + ']'
|
||||
elif b == 'latex':
|
||||
return LATEX % urllib_quote(code)
|
||||
elif b in html_colors:
|
||||
@@ -1426,12 +1457,12 @@ def render(text,
|
||||
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
|
||||
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
|
||||
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
|
||||
id = ' id="%s%s"' % (id_prefix, escape(p)) if p else ''
|
||||
id = ' id="%s%s"' % (id_prefix, local_html_escape(p)) if p else ''
|
||||
beg = (code[:1] == '\n')
|
||||
end = [None, -1][code[-1:] == '\n']
|
||||
if beg and end:
|
||||
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
|
||||
return '<code%s%s>%s</code>' % (cls, id, escape(code[beg:end]))
|
||||
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, local_html_escape(code[1:-1]), pp)
|
||||
return '<code%s%s>%s</code>' % (cls, id, local_html_escape(code[beg:end]))
|
||||
|
||||
text = regex_expand_meta.sub(expand_meta, text)
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ def MemcacheClient(*a, **b):
|
||||
|
||||
class MemcacheClientObj(Client):
|
||||
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
meta_storage = {}
|
||||
max_time_expire = 24*3600
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@ Original author: Zachary Voase
|
||||
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
|
||||
"""
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
from StringIO import StringIO # The pure-Python StringIO supports unicode.
|
||||
import re
|
||||
|
||||
|
||||
|
||||
@@ -8,30 +8,40 @@ Created by: Ross Peoples <ross.peoples@gmail.com>
|
||||
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
|
||||
"""
|
||||
|
||||
import cssmin
|
||||
import jsmin
|
||||
from . import cssmin
|
||||
from . import jsmin
|
||||
import os
|
||||
import hashlib
|
||||
import re
|
||||
import sys
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
if PY2:
|
||||
hashlib_md5 = hashlib.md5
|
||||
else:
|
||||
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
|
||||
|
||||
def open_py23(filename, mode):
|
||||
if PY2:
|
||||
f = open(filename, mode + 'b')
|
||||
else:
|
||||
f = open(filename, mode, encoding="utf8")
|
||||
return f
|
||||
|
||||
def read_binary_file(filename):
|
||||
f = open(filename, 'rb')
|
||||
f = open_py23(filename, 'r')
|
||||
data = f.read()
|
||||
f.close()
|
||||
return data
|
||||
|
||||
|
||||
def write_binary_file(filename, data):
|
||||
f = open(filename, 'wb')
|
||||
f = open_py23(filename, 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
|
||||
def fix_links(css, static_path):
|
||||
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
|
||||
|
||||
|
||||
def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
ignore_concat=[],
|
||||
ignore_minify=['/jquery.js', '/anytime.js']):
|
||||
@@ -109,7 +119,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
js.append(contents)
|
||||
else:
|
||||
js.append(filename)
|
||||
dest_key = hashlib.md5(repr(processed)).hexdigest()
|
||||
dest_key = hashlib_md5(repr(processed)).hexdigest()
|
||||
if css and concat_css:
|
||||
css = '\n\n'.join(contents for contents in css)
|
||||
if not inline_css:
|
||||
|
||||
@@ -137,6 +137,8 @@ def populate_generator(table, default=True, compute=False, contents={}):
|
||||
continue
|
||||
elif field.type == 'upload':
|
||||
continue
|
||||
elif field.compute is not None:
|
||||
continue
|
||||
elif default and not field.default in (None, ''):
|
||||
record[fieldname] = field.default
|
||||
elif compute and field.compute:
|
||||
|
||||
@@ -105,14 +105,14 @@ class ServerProxy(object):
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"pseudo method that can be called"
|
||||
return lambda *args: self.call(attr, *args)
|
||||
return lambda *args, **vars: self.call(attr, *args, **vars)
|
||||
|
||||
def call(self, method, *args):
|
||||
def call(self, method, *args, **vars):
|
||||
"JSON RPC communication (method invocation)"
|
||||
|
||||
# build data sent to the service
|
||||
request_id = random.randint(0, sys.maxsize)
|
||||
data = {'id': request_id, 'method': method, 'params': args, }
|
||||
data = {'id': request_id, 'method': method, 'params': args or vars, }
|
||||
if self.version:
|
||||
data['jsonrpc'] = self.version #mandatory key/value for jsonrpc2 validation else err -32600
|
||||
request = json.dumps(data)
|
||||
|
||||
@@ -673,3 +673,23 @@ def simple_detect(agent):
|
||||
if os_version:
|
||||
os = " ".join((os, os_version))
|
||||
return os, browser
|
||||
|
||||
|
||||
class mobilize(object):
|
||||
"""
|
||||
Decorator for controller functions so they use different views for mobile devices.
|
||||
|
||||
WARNING: If you update httpagentparser make sure to leave mobilize for
|
||||
backwards compatibility.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __call__(self):
|
||||
from gluon import current
|
||||
user_agent = current.request.user_agent()
|
||||
if user_agent.is_mobile:
|
||||
items = current.response.view.split('.')
|
||||
items.insert(-1, 'mobile')
|
||||
current.response.view = '.'.join(items)
|
||||
return self.func()
|
||||
|
||||
@@ -43,6 +43,7 @@ class WebClient(object):
|
||||
self.forms = {}
|
||||
self.history = []
|
||||
self.cookies = {}
|
||||
self.cookiejar = cookielib.CookieJar()
|
||||
self.default_headers = default_headers
|
||||
self.sessions = {}
|
||||
self.session_regex = session_regex and re.compile(session_regex)
|
||||
@@ -79,9 +80,8 @@ class WebClient(object):
|
||||
cookies = cookies or {}
|
||||
headers = headers or {}
|
||||
|
||||
cj = cookielib.CookieJar()
|
||||
args = [
|
||||
urllib2.HTTPCookieProcessor(cj),
|
||||
urllib2.HTTPCookieProcessor(self.cookiejar),
|
||||
urllib2.HTTPHandler(debuglevel=0)
|
||||
]
|
||||
# if required do basic auth
|
||||
|
||||
@@ -94,32 +94,10 @@ import optparse
|
||||
import time
|
||||
import sys
|
||||
import gluon.utils
|
||||
|
||||
if (sys.version_info[0] == 2):
|
||||
from urllib import urlencode, urlopen
|
||||
def to_bytes(obj, charset='utf-8', errors='strict'):
|
||||
if obj is None:
|
||||
return None
|
||||
if isinstance(obj, (bytes, bytearray, buffer)):
|
||||
return bytes(obj)
|
||||
if isinstance(obj, unicode):
|
||||
return obj.encode(charset, errors)
|
||||
raise TypeError('Expected bytes')
|
||||
else:
|
||||
from urllib.request import urlopen
|
||||
from urllib.parse import urlencode
|
||||
def to_bytes(obj, charset='utf-8', errors='strict'):
|
||||
if obj is None:
|
||||
return None
|
||||
if isinstance(obj, (bytes, bytearray, memoryview)):
|
||||
return bytes(obj)
|
||||
if isinstance(obj, str):
|
||||
return obj.encode(charset, errors)
|
||||
raise TypeError('Expected bytes')
|
||||
from gluon._compat import to_native, to_bytes, urlencode, urlopen
|
||||
|
||||
listeners, names, tokens = {}, {}, {}
|
||||
|
||||
|
||||
def websocket_send(url, message, hmac_key=None, group='default'):
|
||||
sig = hmac_key and hmac.new(to_bytes(hmac_key), to_bytes(message)).hexdigest() or ''
|
||||
params = urlencode(
|
||||
@@ -138,8 +116,8 @@ class PostHandler(tornado.web.RequestHandler):
|
||||
if hmac_key and not 'signature' in self.request.arguments:
|
||||
self.send_error(401)
|
||||
if 'message' in self.request.arguments:
|
||||
message = self.request.arguments['message'][0]
|
||||
group = self.request.arguments.get('group', ['default'])[0]
|
||||
message = self.request.arguments['message'][0].decode(encoding='UTF-8')
|
||||
group = self.request.arguments.get('group', ['default'])[0].decode(encoding='UTF-8')
|
||||
print('%s:MESSAGE to %s:%s' % (time.time(), group, message))
|
||||
if hmac_key:
|
||||
signature = self.request.arguments['signature'][0]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
Support for smart import syntax for web2py applications
|
||||
-------------------------------------------------------
|
||||
"""
|
||||
from gluon._compat import builtin, unicodeT, PY2, to_native
|
||||
from gluon._compat import builtin, unicodeT, PY2, to_native, reload
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
@@ -75,7 +75,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
||||
try:
|
||||
oname = name if not name.startswith('.') else '.'+name
|
||||
return NATIVE_IMPORTER(oname, globals, locals, fromlist, level)
|
||||
except ImportError:
|
||||
except (ImportError, KeyError):
|
||||
items = current.request.folder.split(os.path.sep)
|
||||
if not items[-1]:
|
||||
items = items[:-1]
|
||||
@@ -100,7 +100,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
||||
import_tb = sys.exc_info()[2]
|
||||
try:
|
||||
return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
|
||||
except ImportError as e3:
|
||||
except (ImportError, KeyError) as e3:
|
||||
raise ImportError(e1, import_tb) # there an import error in the module
|
||||
except Exception as e2:
|
||||
raise # there is an error in the module
|
||||
|
||||
99
gluon/dal.py
99
gluon/dal.py
@@ -14,6 +14,12 @@ from pydal import DAL as DAL
|
||||
from pydal import Field
|
||||
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
||||
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||
from pydal.migrator import Migrator, InDBMigrator
|
||||
from gluon.serializers import custom_json, xml
|
||||
from gluon.utils import web2py_uuid
|
||||
from gluon import sqlhtml
|
||||
from pydal.drivers import DRIVERS
|
||||
|
||||
|
||||
def _default_validators(db, field):
|
||||
"""
|
||||
@@ -26,7 +32,11 @@ def _default_validators(db, field):
|
||||
field_type, field_length = field.type, field.length
|
||||
requires = []
|
||||
|
||||
if field_type in (('string', 'text', 'password')):
|
||||
if isinstance(field.options, list) and field.requires:
|
||||
requires = validators.IS_IN_SET(field.options, multiple=field_type.startswith('list:'))
|
||||
elif field.regex and not field.requires:
|
||||
requires.append(validators.IS_REGEX(regex))
|
||||
elif field_type in (('string', 'text', 'password')):
|
||||
requires.append(validators.IS_LENGTH(field_length))
|
||||
elif field_type == 'json':
|
||||
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
|
||||
@@ -44,48 +54,64 @@ def _default_validators(db, field):
|
||||
requires.append(validators.IS_TIME())
|
||||
elif field_type == 'datetime':
|
||||
requires.append(validators.IS_DATETIME())
|
||||
elif db and field_type.startswith('reference') and \
|
||||
field_type.find('.') < 0 and \
|
||||
field_type[10:] in db.tables:
|
||||
referenced = db[field_type[10:]]
|
||||
if hasattr(referenced, '_format') and referenced._format:
|
||||
requires = validators.IS_IN_DB(db, referenced._id,
|
||||
referenced._format)
|
||||
if field.unique:
|
||||
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||
if field.tablename == field_type[10:]:
|
||||
return validators.IS_EMPTY_OR(requires)
|
||||
return requires
|
||||
elif db and field_type.startswith('list:reference') and \
|
||||
field_type.find('.') < 0 and \
|
||||
field_type[15:] in db.tables:
|
||||
referenced = db[field_type[15:]]
|
||||
if hasattr(referenced, '_format') and referenced._format:
|
||||
requires = validators.IS_IN_DB(db, referenced._id,
|
||||
referenced._format, multiple=True)
|
||||
else:
|
||||
requires = validators.IS_IN_DB(db, referenced._id,
|
||||
multiple=True)
|
||||
elif db and field_type.startswith('reference'):
|
||||
if field_type.find('.') < 0 and field_type[10:] in db.tables:
|
||||
referenced = db[field_type[10:]]
|
||||
if hasattr(referenced, '_format') and referenced._format:
|
||||
requires = validators.IS_IN_DB(db, referenced._id,referenced._format)
|
||||
else:
|
||||
requires = validators.IS_IN_DB(db, referenced._id)
|
||||
elif field_type.find('.') > 0 and field_type[10:].split('.')[0] in db.tables:
|
||||
table_field = field_type[10:].split('.')
|
||||
table_name=table_field[0]
|
||||
field_name=table_field[1]
|
||||
referenced = db[table_name]
|
||||
if hasattr(referenced, '_format') and referenced._format:
|
||||
requires = validators.IS_IN_DB(db, referenced[field_name],referenced._format)
|
||||
else:
|
||||
requires = validators.IS_IN_DB(db, referenced[field_name])
|
||||
if field.unique:
|
||||
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||
if not field.notnull:
|
||||
requires = validators.IS_EMPTY_OR(requires)
|
||||
return requires
|
||||
elif db and field_type.startswith('list:reference'):
|
||||
if field_type.find('.') < 0 and field_type[15:] in db.tables:
|
||||
referenced = db[field_type[15:]]
|
||||
if hasattr(referenced, '_format') and referenced._format:
|
||||
requires = validators.IS_IN_DB(db, referenced._id,
|
||||
referenced._format, multiple=True)
|
||||
else:
|
||||
requires = validators.IS_IN_DB(db, referenced._id,
|
||||
multiple=True)
|
||||
elif field_type.find('.') > 0 and field_type[15:].split('.')[0] in db.tables:
|
||||
table_field = field_type[15:].split('.')
|
||||
table_name=table_field[0]
|
||||
field_name=table_field[1]
|
||||
referenced = db[table_name]
|
||||
if hasattr(referenced, '_format') and referenced._format:
|
||||
requires = validators.IS_IN_DB(db, referenced[field_name],
|
||||
referenced._format, multiple=True)
|
||||
else:
|
||||
requires = validators.IS_IN_DB(db, referenced[field_name],
|
||||
multiple=True)
|
||||
if field.unique:
|
||||
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||
if not field.notnull:
|
||||
requires = validators.IS_EMPTY_OR(requires)
|
||||
return requires
|
||||
# does not get here for reference and list:reference
|
||||
if field.unique:
|
||||
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
|
||||
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
|
||||
if (field.notnull or field.unique) and field_type not in excluded_fields:
|
||||
requires.insert(0, validators.IS_NOT_EMPTY())
|
||||
elif not field.notnull and not field.unique and requires:
|
||||
requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
|
||||
if isinstance(requires, list):
|
||||
if field.unique:
|
||||
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
|
||||
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
|
||||
if (field.notnull or field.unique) and field_type not in excluded_fields:
|
||||
requires.insert(0, validators.IS_NOT_EMPTY())
|
||||
elif not field.notnull and not field.unique and requires:
|
||||
null = null='' if field.type in ('string', 'text', 'password') else None
|
||||
requires[0] = validators.IS_EMPTY_OR(requires[0], null=null)
|
||||
return requires
|
||||
|
||||
from gluon.serializers import custom_json, xml
|
||||
from gluon.utils import web2py_uuid
|
||||
from gluon import sqlhtml
|
||||
|
||||
|
||||
DAL.serializers = {'json': custom_json, 'xml': xml}
|
||||
DAL.validators_method = _default_validators
|
||||
DAL.uuid = lambda x: web2py_uuid()
|
||||
@@ -96,8 +122,7 @@ DAL.representers = {
|
||||
DAL.Field = Field
|
||||
DAL.Table = Table
|
||||
|
||||
#: add web2py contrib drivers to pyDAL
|
||||
from pydal.drivers import DRIVERS
|
||||
# add web2py contrib drivers to pyDAL
|
||||
if not DRIVERS.get('pymysql'):
|
||||
try:
|
||||
from .contrib import pymysql
|
||||
|
||||
@@ -89,7 +89,7 @@ def communicate(command=None):
|
||||
|
||||
# New debugger implementation using dbg and a web UI
|
||||
|
||||
import gluon.contrib.dbg as dbg
|
||||
import gluon.contrib.dbg as c_dbg
|
||||
from threading import RLock
|
||||
|
||||
interact_lock = RLock()
|
||||
@@ -109,11 +109,11 @@ def check_interaction(fn):
|
||||
return check_fn
|
||||
|
||||
|
||||
class WebDebugger(dbg.Frontend):
|
||||
class WebDebugger(c_dbg.Frontend):
|
||||
"""Qdb web2py interface"""
|
||||
|
||||
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None):
|
||||
dbg.Frontend.__init__(self, pipe)
|
||||
c_dbg.Frontend.__init__(self, pipe)
|
||||
self.clear_interaction()
|
||||
|
||||
def clear_interaction(self):
|
||||
@@ -128,7 +128,7 @@ class WebDebugger(dbg.Frontend):
|
||||
run_lock.acquire()
|
||||
try:
|
||||
while self.pipe.poll():
|
||||
dbg.Frontend.run(self)
|
||||
c_dbg.Frontend.run(self)
|
||||
finally:
|
||||
run_lock.release()
|
||||
|
||||
@@ -149,23 +149,23 @@ class WebDebugger(dbg.Frontend):
|
||||
|
||||
@check_interaction
|
||||
def do_continue(self):
|
||||
dbg.Frontend.do_continue(self)
|
||||
c_dbg.Frontend.do_continue(self)
|
||||
|
||||
@check_interaction
|
||||
def do_step(self):
|
||||
dbg.Frontend.do_step(self)
|
||||
c_dbg.Frontend.do_step(self)
|
||||
|
||||
@check_interaction
|
||||
def do_return(self):
|
||||
dbg.Frontend.do_return(self)
|
||||
c_dbg.Frontend.do_return(self)
|
||||
|
||||
@check_interaction
|
||||
def do_next(self):
|
||||
dbg.Frontend.do_next(self)
|
||||
c_dbg.Frontend.do_next(self)
|
||||
|
||||
@check_interaction
|
||||
def do_quit(self):
|
||||
dbg.Frontend.do_quit(self)
|
||||
c_dbg.Frontend.do_quit(self)
|
||||
|
||||
def do_exec(self, statement):
|
||||
interact_lock.acquire()
|
||||
@@ -175,18 +175,18 @@ class WebDebugger(dbg.Frontend):
|
||||
# avoid spurious interaction notifications:
|
||||
self.set_burst(2)
|
||||
# execute the statement in the remote debugger:
|
||||
return dbg.Frontend.do_exec(self, statement)
|
||||
return c_dbg.Frontend.do_exec(self, statement)
|
||||
finally:
|
||||
interact_lock.release()
|
||||
|
||||
# create the connection between threads:
|
||||
|
||||
parent_queue, child_queue = Queue.Queue(), Queue.Queue()
|
||||
front_conn = dbg.QueuePipe("parent", parent_queue, child_queue)
|
||||
child_conn = dbg.QueuePipe("child", child_queue, parent_queue)
|
||||
front_conn = c_dbg.QueuePipe("parent", parent_queue, child_queue)
|
||||
child_conn = c_dbg.QueuePipe("child", child_queue, parent_queue)
|
||||
|
||||
web_debugger = WebDebugger(front_conn) # frontend
|
||||
dbg_debugger = dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
|
||||
dbg_debugger = c_dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
|
||||
dbg = dbg_debugger
|
||||
|
||||
# enable getting context (stack, globals/locals) at interaction
|
||||
|
||||
@@ -9,19 +9,19 @@ Based on http://code.activestate.com/recipes/52257/
|
||||
|
||||
Licensed under the PSF License
|
||||
"""
|
||||
|
||||
from gluon._compat import to_unicode
|
||||
import codecs
|
||||
|
||||
# None represents a potentially variable byte. "##" in the XML spec...
|
||||
autodetect_dict = { # bytepattern : ("name",
|
||||
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
|
||||
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
|
||||
(0xFE, 0xFF, None, None): ("utf_16_be"),
|
||||
(0xFF, 0xFE, None, None): ("utf_16_le"),
|
||||
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
|
||||
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
|
||||
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
|
||||
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
|
||||
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
|
||||
(0xFE, 0xFF, None, None): ("utf_16_be"),
|
||||
(0xFF, 0xFE, None, None): ("utf_16_le"),
|
||||
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
|
||||
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
|
||||
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
|
||||
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@ def autoDetectXMLEncoding(buffer):
|
||||
# buffer at once but otherwise we'd have to decode a character at
|
||||
# a time looking for the quote character...that's a pain
|
||||
|
||||
encoding = "utf_8" # according to the XML spec, this is the default
|
||||
# this code successively tries to refine the default
|
||||
# whenever it fails to refine, it falls back to
|
||||
# the last place encoding was set.
|
||||
encoding = "utf_8"
|
||||
# according to the XML spec, this is the default this code successively tries to refine the default
|
||||
# whenever it fails to refine, it falls back to the last place encoding was set.
|
||||
|
||||
if len(buffer) >= 4:
|
||||
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
|
||||
enc_info = autodetect_dict.get(bytes, None)
|
||||
@@ -51,8 +51,7 @@ def autoDetectXMLEncoding(buffer):
|
||||
enc_info = None
|
||||
|
||||
if enc_info:
|
||||
encoding = enc_info # we've got a guess... these are
|
||||
#the new defaults
|
||||
encoding = enc_info # we've got a guess... these are the new defaults
|
||||
|
||||
# try to find a more precise encoding using xml declaration
|
||||
secret_decoder_ring = codecs.lookup(encoding)[1]
|
||||
@@ -77,4 +76,4 @@ def autoDetectXMLEncoding(buffer):
|
||||
|
||||
def decoder(buffer):
|
||||
encoding = autoDetectXMLEncoding(buffer)
|
||||
return buffer.decode(encoding).encode('utf8')
|
||||
return to_unicode(buffer, charset=encoding)
|
||||
|
||||
@@ -415,10 +415,10 @@ def fix_newlines(path):
|
||||
|\r|
|
||||
)''')
|
||||
for filename in listdir(path, '.*\.(py|html)$', drop=False):
|
||||
rdata = read_file(filename, 'rb')
|
||||
rdata = read_file(filename, 'r')
|
||||
wdata = regex.sub('\n', rdata)
|
||||
if wdata != rdata:
|
||||
write_file(filename, wdata, 'wb')
|
||||
write_file(filename, wdata, 'w')
|
||||
|
||||
|
||||
def copystream(
|
||||
|
||||
153
gluon/globals.py
153
gluon/globals.py
@@ -13,7 +13,8 @@ Contains the classes for the global used variables:
|
||||
- Session
|
||||
|
||||
"""
|
||||
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, unicodeT, long
|
||||
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \
|
||||
unicodeT, long, hashlib_md5, urllib_quote
|
||||
from gluon.storage import Storage, List
|
||||
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
|
||||
from gluon.contenttype import contenttype
|
||||
@@ -30,7 +31,7 @@ from gluon.fileutils import copystream
|
||||
import hashlib
|
||||
from pydal.contrib import portalocker
|
||||
from pickle import Pickler, MARK, DICT, EMPTY_DICT
|
||||
#from types import DictionaryType
|
||||
# from types import DictionaryType
|
||||
import datetime
|
||||
import re
|
||||
import os
|
||||
@@ -48,7 +49,7 @@ PAST = 'Sat, 1-Jan-1971 00:00:00'
|
||||
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
|
||||
|
||||
try:
|
||||
#FIXME PY3
|
||||
# FIXME PY3
|
||||
from gluon.contrib.minify import minify
|
||||
have_minify = True
|
||||
except ImportError:
|
||||
@@ -79,6 +80,7 @@ template_mapping = {
|
||||
'js:inline': js_inline
|
||||
}
|
||||
|
||||
|
||||
# IMPORTANT:
|
||||
# this is required so that pickled dict(s) and class.__dict__
|
||||
# are sorted and web2py can detect without ambiguity when a session changes
|
||||
@@ -89,9 +91,11 @@ class SortingPickler(Pickler):
|
||||
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
|
||||
|
||||
if PY2:
|
||||
#FIXME PY3
|
||||
SortingPickler.dispatch = copy.copy(Pickler.dispatch)
|
||||
SortingPickler.dispatch[dict] = SortingPickler.save_dict
|
||||
else:
|
||||
SortingPickler.dispatch_table = copyreg.dispatch_table.copy()
|
||||
SortingPickler.dispatch_table[dict] = SortingPickler.save_dict
|
||||
|
||||
|
||||
def sorting_dumps(obj, protocol=None):
|
||||
@@ -119,7 +123,7 @@ def copystream_progress(request, chunk_size=10 ** 5):
|
||||
dest = tempfile.NamedTemporaryFile()
|
||||
except NotImplementedError: # and GAE this
|
||||
dest = tempfile.TemporaryFile()
|
||||
if not 'X-Progress-ID' in request.get_vars:
|
||||
if 'X-Progress-ID' not in request.get_vars:
|
||||
copystream(source, dest, size, chunk_size)
|
||||
return dest
|
||||
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
|
||||
@@ -197,7 +201,8 @@ class Request(Storage):
|
||||
"""Takes the QUERY_STRING and unpacks it to get_vars
|
||||
"""
|
||||
query_string = self.env.get('query_string', '')
|
||||
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
|
||||
dget = urlparse.parse_qs(query_string, keep_blank_values=1)
|
||||
# Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
|
||||
get_vars = self._get_vars = Storage(dget)
|
||||
for (key, value) in iteritems(get_vars):
|
||||
if isinstance(value, list) and len(value) == 1:
|
||||
@@ -215,7 +220,12 @@ class Request(Storage):
|
||||
|
||||
if is_json:
|
||||
try:
|
||||
json_vars = json_parser.load(body)
|
||||
# In Python 3 versions prior to 3.6 load doesn't accept bytes and
|
||||
# bytearray, so we read the body convert to native and use loads
|
||||
# instead of load.
|
||||
# This line can be simplified to json_vars = json_parser.load(body)
|
||||
# if and when we drop support for python versions under 3.6
|
||||
json_vars = json_parser.loads(to_native(body.read()))
|
||||
except:
|
||||
# incoherent request bodies can still be parsed "ad-hoc"
|
||||
json_vars = {}
|
||||
@@ -227,8 +237,7 @@ class Request(Storage):
|
||||
body.seek(0)
|
||||
|
||||
# parse POST variables on POST, PUT, BOTH only in post_vars
|
||||
if (body and not is_json
|
||||
and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
|
||||
if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'):
|
||||
query_string = env.pop('QUERY_STRING', None)
|
||||
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
|
||||
try:
|
||||
@@ -327,11 +336,16 @@ class Request(Storage):
|
||||
user_agent = session._user_agent
|
||||
if user_agent:
|
||||
return user_agent
|
||||
user_agent = user_agent_parser.detect(self.env.http_user_agent)
|
||||
http_user_agent = self.env.http_user_agent or ''
|
||||
user_agent = user_agent_parser.detect(http_user_agent)
|
||||
for key, value in user_agent.items():
|
||||
if isinstance(value, dict):
|
||||
user_agent[key] = Storage(value)
|
||||
user_agent = session._user_agent = Storage(user_agent)
|
||||
user_agent = Storage(user_agent)
|
||||
user_agent.is_mobile = 'Mobile' in http_user_agent
|
||||
user_agent.is_tablet = 'Tablet' in http_user_agent
|
||||
session._user_agent = user_agent
|
||||
|
||||
return user_agent
|
||||
|
||||
def requires_https(self):
|
||||
@@ -349,14 +363,14 @@ class Request(Storage):
|
||||
current.session.forget()
|
||||
redirect(URL(scheme='https', args=self.args, vars=self.vars))
|
||||
|
||||
def restful(self):
|
||||
def restful(self, ignore_extension=False):
|
||||
def wrapper(action, request=self):
|
||||
def f(_action=action, *a, **b):
|
||||
request.is_restful = True
|
||||
env = request.env
|
||||
is_json = env.content_type=='application/json'
|
||||
is_json = env.content_type == 'application/json'
|
||||
method = env.request_method
|
||||
if len(request.args) and '.' in request.args[-1]:
|
||||
if not ignore_extension and len(request.args) and '.' in request.args[-1]:
|
||||
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
|
||||
current.response.headers['Content-Type'] = \
|
||||
contenttype('.' + request.extension.lower())
|
||||
@@ -415,7 +429,6 @@ class Response(Storage):
|
||||
if not escape:
|
||||
self.body.write(str(data))
|
||||
else:
|
||||
# FIXME PY3:
|
||||
self.body.write(to_native(xmlescape(data)))
|
||||
|
||||
def render(self, *a, **b):
|
||||
@@ -451,13 +464,13 @@ class Response(Storage):
|
||||
for meta in iteritems((self.meta or {})):
|
||||
k, v = meta
|
||||
if isinstance(v, dict):
|
||||
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), to_native(xmlescape(v[key]))) for key in v) +' />\n'
|
||||
s += '<meta' + ''.join(' %s="%s"' % (to_native(xmlescape(key)),
|
||||
to_native(xmlescape(v[key]))) for key in v) + ' />\n'
|
||||
else:
|
||||
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
|
||||
self.write(s, escape=False)
|
||||
|
||||
def include_files(self, extensions=None):
|
||||
|
||||
"""
|
||||
Includes files (usually in the head).
|
||||
Can minify and cache local files
|
||||
@@ -465,46 +478,67 @@ class Response(Storage):
|
||||
response.cache_includes = (cache_method, time_expire).
|
||||
Example: (cache.disk, 60) # caches to disk for 1 minute.
|
||||
"""
|
||||
app = current.request.application
|
||||
|
||||
# We start by building a files list in which adjacent files internal to
|
||||
# the application are placed in a list inside the files list.
|
||||
#
|
||||
# We will only minify and concat adjacent internal files as there's
|
||||
# no way to know if changing the order with which the files are apppended
|
||||
# will break things since the order matters in both CSS and JS and
|
||||
# internal files may be interleaved with external ones.
|
||||
files = []
|
||||
ext_files = []
|
||||
has_js = has_css = False
|
||||
# For the adjacent list we're going to use storage List to both distinguish
|
||||
# from the regular list and so we can add attributes
|
||||
internal = List()
|
||||
internal.has_js = False
|
||||
internal.has_css = False
|
||||
done = set() # to remove duplicates
|
||||
for item in self.files:
|
||||
if isinstance(item, (list, tuple)):
|
||||
ext_files.append(item)
|
||||
if not isinstance(item, list):
|
||||
if item in done:
|
||||
continue
|
||||
done.add(item)
|
||||
if isinstance(item, (list, tuple)) or not item.startswith('/' + app): # also consider items in other web2py applications to be external
|
||||
if internal:
|
||||
files.append(internal)
|
||||
internal = List()
|
||||
internal.has_js = False
|
||||
internal.has_css = False
|
||||
files.append(item)
|
||||
continue
|
||||
if extensions and not item.rpartition('.')[2] in extensions:
|
||||
continue
|
||||
if item in files:
|
||||
continue
|
||||
internal.append(item)
|
||||
if item.endswith('.js'):
|
||||
has_js = True
|
||||
internal.has_js = True
|
||||
if item.endswith('.css'):
|
||||
has_css = True
|
||||
files.append(item)
|
||||
internal.has_css = True
|
||||
if internal:
|
||||
files.append(internal)
|
||||
|
||||
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
|
||||
# cache for 5 minutes by default
|
||||
key = hashlib.md5(repr(files)).hexdigest()
|
||||
|
||||
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
||||
|
||||
def call_minify(files=files):
|
||||
return minify.minify(files,
|
||||
URL('static', 'temp'),
|
||||
current.request.folder,
|
||||
self.optimize_css,
|
||||
self.optimize_js)
|
||||
if cache:
|
||||
cache_model, time_expire = cache
|
||||
files = cache_model('response.files.minified/' + key,
|
||||
call_minify,
|
||||
time_expire)
|
||||
else:
|
||||
files = call_minify()
|
||||
|
||||
files.extend(ext_files)
|
||||
s = []
|
||||
for item in files:
|
||||
# We're done we can now minify
|
||||
if have_minify:
|
||||
for i, f in enumerate(files):
|
||||
if isinstance(f, List) and ((self.optimize_css and f.has_css) or (self.optimize_js and f.has_js)):
|
||||
# cache for 5 minutes by default
|
||||
key = hashlib_md5(repr(f)).hexdigest()
|
||||
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
||||
def call_minify(files=f):
|
||||
return List(minify.minify(files,
|
||||
URL('static', 'temp'),
|
||||
current.request.folder,
|
||||
self.optimize_css,
|
||||
self.optimize_js))
|
||||
if cache:
|
||||
cache_model, time_expire = cache
|
||||
files[i] = cache_model('response.files.minified/' + key,
|
||||
call_minify,
|
||||
time_expire)
|
||||
else:
|
||||
files[i] = call_minify()
|
||||
|
||||
def static_map(s, item):
|
||||
if isinstance(item, str):
|
||||
f = item.lower().split('?')[0]
|
||||
ext = f.rpartition('.')[2]
|
||||
@@ -523,6 +557,14 @@ class Response(Storage):
|
||||
tmpl = template_mapping.get(f)
|
||||
if tmpl:
|
||||
s.append(tmpl % item[1])
|
||||
|
||||
s = []
|
||||
for item in files:
|
||||
if isinstance(item, List):
|
||||
for f in item:
|
||||
static_map(s, f)
|
||||
else:
|
||||
static_map(s, item)
|
||||
self.write(''.join(s), escape=False)
|
||||
|
||||
def stream(self,
|
||||
@@ -578,9 +620,9 @@ class Response(Storage):
|
||||
if hasattr(stream, 'name'):
|
||||
filename = stream.name
|
||||
|
||||
if filename and not 'content-type' in keys:
|
||||
if filename and 'content-type' not in keys:
|
||||
headers['Content-Type'] = contenttype(filename)
|
||||
if filename and not 'content-length' in keys:
|
||||
if filename and 'content-length' not in keys:
|
||||
try:
|
||||
headers['Content-Length'] = \
|
||||
os.path.getsize(filename)
|
||||
@@ -638,6 +680,11 @@ class Response(Storage):
|
||||
if download_filename is None:
|
||||
download_filename = filename
|
||||
if attachment:
|
||||
# Browsers still don't have a simple uniform way to have non ascii
|
||||
# characters in the filename so for now we are percent encoding it
|
||||
if isinstance(download_filename, unicodeT):
|
||||
download_filename = download_filename.encode('utf-8')
|
||||
download_filename = urllib_quote(download_filename)
|
||||
headers['Content-Disposition'] = \
|
||||
'attachment; filename="%s"' % download_filename.replace('"', '\"')
|
||||
return self.stream(stream, chunk_size=chunk_size, request=request)
|
||||
@@ -1022,7 +1069,7 @@ class Session(Storage):
|
||||
if self._forget:
|
||||
del rcookies[response.session_id_name]
|
||||
return
|
||||
if self.get('httponly_cookies',True):
|
||||
if self.get('httponly_cookies', True):
|
||||
scookies['HttpOnly'] = True
|
||||
if self._secure:
|
||||
scookies['secure'] = True
|
||||
@@ -1193,7 +1240,7 @@ class Session(Storage):
|
||||
if (not response.session_id or
|
||||
not response.session_filename or
|
||||
self._forget
|
||||
or self._unchanged(response)):
|
||||
or self._unchanged(response)):
|
||||
# self.clear_session_cookies()
|
||||
return False
|
||||
else:
|
||||
|
||||
@@ -182,8 +182,8 @@ class Highlighter(object):
|
||||
)),
|
||||
'PYTHONMultilineString': (python_tokenizer,
|
||||
(('ENDMULTILINESTRING',
|
||||
re.compile(r'.*?("""|\'\'\')',
|
||||
re.DOTALL), 'color: darkred'), )),
|
||||
re.compile(r'.*?("""|\'\'\')',
|
||||
re.DOTALL), 'color: darkred'), )),
|
||||
'HTML': (html_tokenizer, (
|
||||
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
|
||||
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
|
||||
@@ -209,7 +209,7 @@ class Highlighter(object):
|
||||
mode = self.mode
|
||||
while i < len(data):
|
||||
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
|
||||
if not token in self.suppress_tokens:
|
||||
if token not in self.suppress_tokens:
|
||||
match = o_re.match(data, i)
|
||||
if match:
|
||||
if style:
|
||||
@@ -221,7 +221,7 @@ class Highlighter(object):
|
||||
new_mode = \
|
||||
Highlighter.all_styles[mode][0](self,
|
||||
token, match, style)
|
||||
if not new_mode is None:
|
||||
if new_mode is not None:
|
||||
mode = new_mode
|
||||
i += max(1, len(match.group()))
|
||||
break
|
||||
@@ -241,9 +241,9 @@ class Highlighter(object):
|
||||
style = self.styles[token]
|
||||
if self.span_style != style:
|
||||
if style != 'Keep':
|
||||
if not self.span_style is None:
|
||||
if self.span_style is not None:
|
||||
self.output.append('</span>')
|
||||
if not style is None:
|
||||
if style is not None:
|
||||
self.output.append('<span style="%s">' % style)
|
||||
self.span_style = style
|
||||
|
||||
@@ -260,7 +260,7 @@ def highlight(
|
||||
):
|
||||
styles = styles or {}
|
||||
attributes = attributes or {}
|
||||
if not 'CODE' in styles:
|
||||
if 'CODE' not in styles:
|
||||
code_style = """
|
||||
font-size: 11px;
|
||||
font-family: Bitstream Vera Sans Mono,monospace;
|
||||
@@ -272,7 +272,7 @@ def highlight(
|
||||
white-space: pre !important;\n"""
|
||||
else:
|
||||
code_style = styles['CODE']
|
||||
if not 'LINENUMBERS' in styles:
|
||||
if 'LINENUMBERS' not in styles:
|
||||
linenumbers_style = """
|
||||
font-size: 11px;
|
||||
font-family: Bitstream Vera Sans Mono,monospace;
|
||||
@@ -283,7 +283,7 @@ def highlight(
|
||||
color: #A0A0A0;\n"""
|
||||
else:
|
||||
linenumbers_style = styles['LINENUMBERS']
|
||||
if not 'LINEHIGHLIGHT' in styles:
|
||||
if 'LINEHIGHLIGHT' not in styles:
|
||||
linehighlight_style = "background-color: #EBDDE2;"
|
||||
else:
|
||||
linehighlight_style = styles['LINEHIGHLIGHT']
|
||||
@@ -333,8 +333,9 @@ def highlight(
|
||||
== '_' and value])
|
||||
if fa:
|
||||
fa = ' ' + fa
|
||||
return '<table%s><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
|
||||
% (fa, linenumbers_style, numbers, code_style, code)
|
||||
return '<table%s><tr style="vertical-align:top;">' \
|
||||
'<td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td>' \
|
||||
'<td><pre style="%s">%s</pre></td></tr></table>' % (fa, linenumbers_style, numbers, code_style, code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -342,5 +343,4 @@ if __name__ == '__main__':
|
||||
argfp = open(sys.argv[1])
|
||||
data = argfp.read()
|
||||
argfp.close()
|
||||
print('<html><body>' + highlight(data, sys.argv[2])\
|
||||
+ '</body></html>')
|
||||
print('<html><body>' + highlight(data, sys.argv[2]) + '</body></html>')
|
||||
|
||||
@@ -20,7 +20,8 @@ import urllib
|
||||
import base64
|
||||
from gluon import sanitizer, decoder
|
||||
import itertools
|
||||
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type
|
||||
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
|
||||
urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
|
||||
from gluon.utils import local_html_escape
|
||||
import marshal
|
||||
|
||||
@@ -109,6 +110,7 @@ __all__ = [
|
||||
|
||||
DEFAULT_PASSWORD_DISPLAY = '*' * 8
|
||||
|
||||
|
||||
def xmlescape(data, quote=True):
|
||||
"""
|
||||
Returns an escaped string of the provided data
|
||||
@@ -124,10 +126,9 @@ def xmlescape(data, quote=True):
|
||||
|
||||
if not(isinstance(data, (text_type, bytes))):
|
||||
# i.e., integers
|
||||
data=str(data)
|
||||
data = str(data)
|
||||
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
|
||||
|
||||
|
||||
# ... and do the escaping
|
||||
data = local_html_escape(data, quote)
|
||||
return data
|
||||
@@ -596,10 +597,10 @@ class XML(XmlComponent):
|
||||
for A, IMG and BlockQuote).
|
||||
The key is the tag; the value is a list of allowed attributes.
|
||||
"""
|
||||
if sanitize:
|
||||
text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
|
||||
if isinstance(text, unicodeT):
|
||||
text = to_native(text.encode('utf8', 'xmlcharrefreplace'))
|
||||
if sanitize:
|
||||
text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
|
||||
elif isinstance(text, bytes):
|
||||
text = to_native(text)
|
||||
elif not isinstance(text, str):
|
||||
@@ -671,6 +672,7 @@ def XML_pickle(data):
|
||||
return XML_unpickle, (marshal.dumps(str(data)),)
|
||||
copyreg.pickle(XML, XML_pickle, XML_unpickle)
|
||||
|
||||
|
||||
@implements_bool
|
||||
class DIV(XmlComponent):
|
||||
"""
|
||||
@@ -998,9 +1000,9 @@ class DIV(XmlComponent):
|
||||
if isinstance(c, XmlComponent):
|
||||
s = c.flatten(render)
|
||||
elif render:
|
||||
s = render(str(c))
|
||||
s = render(to_native(c))
|
||||
else:
|
||||
s = str(c)
|
||||
s = to_native(c)
|
||||
text += s
|
||||
if render:
|
||||
text = render(text, self.tag, self.attributes)
|
||||
@@ -1281,7 +1283,6 @@ class __TAG__(XmlComponent):
|
||||
def __getattr__(self, name):
|
||||
if name[-1:] == '_':
|
||||
name = name[:-1] + '/'
|
||||
name=to_bytes(name)
|
||||
return lambda *a, **b: __tag_div__(name, *a, **b)
|
||||
|
||||
def __call__(self, html):
|
||||
@@ -1310,8 +1311,10 @@ class HTML(DIV):
|
||||
tag = b'html'
|
||||
|
||||
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
|
||||
transitional = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
|
||||
frameset = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
|
||||
transitional = \
|
||||
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
|
||||
frameset = \
|
||||
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
|
||||
html5 = b'<!DOCTYPE HTML>\n'
|
||||
|
||||
def xml(self):
|
||||
@@ -1416,20 +1419,21 @@ class LINK(DIV):
|
||||
|
||||
class SCRIPT(DIV):
|
||||
|
||||
tag = 'script'
|
||||
tag = b'script'
|
||||
tagname = to_bytes(tag)
|
||||
|
||||
def xml(self):
|
||||
(fa, co) = self._xml()
|
||||
fa = to_native(fa)
|
||||
fa = to_bytes(fa)
|
||||
# no escaping of subcomponents
|
||||
co = '\n'.join([str(component) for component in
|
||||
co = b'\n'.join([to_bytes(component) for component in
|
||||
self.components])
|
||||
if co:
|
||||
# <script [attributes]><!--//--><![CDATA[//><!--
|
||||
# script body
|
||||
# //--><!]]></script>
|
||||
# return '<%s%s><!--//--><![CDATA[//><!--\n%s\n//--><!]]></%s>' % (self.tag, fa, co, self.tag)
|
||||
return '<%s%s><!--\n%s\n//--></%s>' % (self.tag, fa, co, self.tag)
|
||||
return b'<%s%s><!--\n%s\n//--></%s>' % (self.tagname, fa, co, self.tagname)
|
||||
else:
|
||||
return DIV.xml(self)
|
||||
|
||||
@@ -1437,18 +1441,19 @@ class SCRIPT(DIV):
|
||||
class STYLE(DIV):
|
||||
|
||||
tag = 'style'
|
||||
tagname = to_bytes(tag)
|
||||
|
||||
def xml(self):
|
||||
(fa, co) = self._xml()
|
||||
fa = to_native(fa)
|
||||
fa = to_bytes(fa)
|
||||
# no escaping of subcomponents
|
||||
co = '\n'.join([str(component) for component in
|
||||
co = b'\n'.join([to_bytes(component) for component in
|
||||
self.components])
|
||||
if co:
|
||||
# <style [attributes]><!--/*--><![CDATA[/*><!--*/
|
||||
# style body
|
||||
# /*]]>*/--></style>
|
||||
return '<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tag, fa, co, self.tag)
|
||||
return b'<%s%s><!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/--></%s>' % (self.tagname, fa, co, self.tagname)
|
||||
else:
|
||||
return DIV.xml(self)
|
||||
|
||||
@@ -1862,7 +1867,7 @@ class INPUT(DIV):
|
||||
except:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
msg = "Validation error, field:%s %s" % (name,validator)
|
||||
msg = "Validation error, field:%s %s" % (name, validator)
|
||||
raise Exception(msg)
|
||||
if errors is not None:
|
||||
self.vars[name] = value
|
||||
@@ -1912,7 +1917,7 @@ class INPUT(DIV):
|
||||
name = self.attributes.get('_name', None)
|
||||
if name and hasattr(self, 'errors') \
|
||||
and self.errors.get(name, None) \
|
||||
and self['hideerror'] != True:
|
||||
and self['hideerror'] is not True:
|
||||
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
|
||||
return DIV.xml(self) + DIV(
|
||||
DIV(
|
||||
@@ -1980,7 +1985,6 @@ class OPTGROUP(DIV):
|
||||
|
||||
|
||||
class SELECT(INPUT):
|
||||
|
||||
"""
|
||||
Examples:
|
||||
|
||||
@@ -2015,7 +2019,7 @@ class SELECT(INPUT):
|
||||
if value is not None:
|
||||
if not self['_multiple']:
|
||||
for c in options: # my patch
|
||||
if ((value is not None) and (str(c['_value']) == str(value))):
|
||||
if (value is not None) and (str(c['_value']) == str(value)):
|
||||
c['_selected'] = 'selected'
|
||||
else:
|
||||
c['_selected'] = None
|
||||
@@ -2025,7 +2029,7 @@ class SELECT(INPUT):
|
||||
else:
|
||||
values = [str(value)]
|
||||
for c in options: # my patch
|
||||
if ((value is not None) and (str(c['_value']) in values)):
|
||||
if (value is not None) and (str(c['_value']) in values):
|
||||
c['_selected'] = 'selected'
|
||||
else:
|
||||
c['_selected'] = None
|
||||
@@ -2376,22 +2380,21 @@ class FORM(DIV):
|
||||
|
||||
def as_json(self, sanitize=True):
|
||||
d = self.as_dict(flat=True, sanitize=sanitize)
|
||||
from serializers import json
|
||||
from gluon.serializers import json
|
||||
return json(d)
|
||||
|
||||
def as_yaml(self, sanitize=True):
|
||||
d = self.as_dict(flat=True, sanitize=sanitize)
|
||||
from serializers import yaml
|
||||
from gluon.serializers import yaml
|
||||
return yaml(d)
|
||||
|
||||
def as_xml(self, sanitize=True):
|
||||
d = self.as_dict(flat=True, sanitize=sanitize)
|
||||
from serializers import xml
|
||||
from gluon.serializers import xml
|
||||
return xml(d)
|
||||
|
||||
|
||||
class BEAUTIFY(DIV):
|
||||
|
||||
"""
|
||||
Turns any list, dictionary, etc into decent looking html.
|
||||
|
||||
@@ -2430,7 +2433,7 @@ class BEAUTIFY(DIV):
|
||||
if level == 0:
|
||||
return
|
||||
for c in self.components:
|
||||
if hasattr(c, 'value') and not callable(c.value):
|
||||
if hasattr(c, 'value') and not callable(c.value) and not isinstance(c, cgi.FieldStorage):
|
||||
if c.value:
|
||||
components.append(c.value)
|
||||
if hasattr(c, 'xml') and callable(c.xml):
|
||||
@@ -2548,7 +2551,7 @@ class MENU(DIV):
|
||||
li['_class'] = li['_class'] + ' ' + self['li_active']
|
||||
else:
|
||||
li['_class'] = self['li_active']
|
||||
if len(item) <= 4 or item[4] == True:
|
||||
if len(item) <= 4 or item[4] is True:
|
||||
ul.append(li)
|
||||
return ul
|
||||
|
||||
@@ -2562,7 +2565,7 @@ class MENU(DIV):
|
||||
# ex: ('', False, A('title', _href=URL(...), _title="title"))
|
||||
# ex: (A('title', _href=URL(...), _title="title"), False, None)
|
||||
custom_items.append(item)
|
||||
elif len(item) <= 4 or item[4] == True:
|
||||
elif len(item) <= 4 or item[4] is True:
|
||||
select.append(OPTION(CAT(prefix, item[0]),
|
||||
_value=item[2], _selected=item[1]))
|
||||
if len(item) > 3 and len(item[3]):
|
||||
@@ -2655,36 +2658,24 @@ class web2pyHTMLParser(HTMLParser):
|
||||
"""
|
||||
obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
|
||||
obj.tree contains the root of the tree, and tree can be manipulated
|
||||
|
||||
>>> str(web2pyHTMLParser('hello<div a="b" c=3>wor<ld<span>xxx</span>y<script/>yy</div>zzz').tree)
|
||||
'hello<div a="b" c="3">wor<ld<span>xxx</span>y<script></script>yy</div>zzz'
|
||||
>>> str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
|
||||
'<div>a<span>b</span></div>c'
|
||||
>>> tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
|
||||
>>> tree.element(_a='b')['_c']=5
|
||||
>>> str(tree)
|
||||
'hello<div a="b" c="5">world</div>'
|
||||
"""
|
||||
|
||||
def __init__(self, text, closed=('input', 'link')):
|
||||
HTMLParser.__init__(self)
|
||||
self.tree = self.parent = TAG['']()
|
||||
self.closed = closed
|
||||
self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
|
||||
self.last = None
|
||||
self.feed(text)
|
||||
|
||||
def handle_starttag(self, tagname, attrs):
|
||||
if tagname.upper() in self.tags:
|
||||
tag = eval(tagname.upper())
|
||||
else:
|
||||
if tagname in self.closed:
|
||||
tagname += '/'
|
||||
tag = TAG[tagname]()
|
||||
if tagname in self.closed:
|
||||
tagname += '/'
|
||||
tag = TAG[tagname]()
|
||||
for key, value in attrs:
|
||||
tag['_' + key] = value
|
||||
tag.parent = self.parent
|
||||
self.parent.append(tag)
|
||||
if not tag.tag.endswith(b'/'):
|
||||
if not tag.tag.endswith('/'):
|
||||
self.parent = tag
|
||||
else:
|
||||
self.last = tag.tag[:-1]
|
||||
@@ -2707,7 +2698,6 @@ class web2pyHTMLParser(HTMLParser):
|
||||
self.parent.append(entitydefs[name])
|
||||
|
||||
def handle_endtag(self, tagname):
|
||||
tagname = to_bytes(tagname)
|
||||
# this deals with unbalanced tags
|
||||
if tagname == self.last:
|
||||
return
|
||||
@@ -2717,7 +2707,8 @@ class web2pyHTMLParser(HTMLParser):
|
||||
self.parent = self.parent.parent
|
||||
except:
|
||||
raise RuntimeError("unable to balance tag %s" % tagname)
|
||||
if parent_tagname[:len(tagname)] == tagname: break
|
||||
if parent_tagname[:len(tagname)] == tagname:
|
||||
break
|
||||
|
||||
|
||||
def markdown_serializer(text, tag=None, attr=None):
|
||||
|
||||
@@ -11,7 +11,7 @@ HTTP statuses helpers
|
||||
"""
|
||||
|
||||
import re
|
||||
from gluon._compat import iteritems
|
||||
from gluon._compat import iteritems, unicodeT, to_bytes
|
||||
|
||||
__all__ = ['HTTP', 'redirect']
|
||||
|
||||
@@ -111,22 +111,29 @@ class HTTP(Exception):
|
||||
if not body:
|
||||
body = status
|
||||
if isinstance(body, (str, bytes, bytearray)):
|
||||
if isinstance(body, unicodeT):
|
||||
body = to_bytes(body) # This must be done before len
|
||||
headers['Content-Length'] = len(body)
|
||||
rheaders = []
|
||||
for k, v in iteritems(headers):
|
||||
if isinstance(v, list):
|
||||
rheaders += [(k, str(item)) for item in v]
|
||||
elif not v is None:
|
||||
elif v is not None:
|
||||
rheaders.append((k, str(v)))
|
||||
responder(status, rheaders)
|
||||
if env.get('request_method', '') == 'HEAD':
|
||||
return ['']
|
||||
elif isinstance(body, (str, bytes, bytearray)):
|
||||
if isinstance(body, unicodeT):
|
||||
body = to_bytes(body)
|
||||
return [body]
|
||||
elif hasattr(body, '__iter__'):
|
||||
return body
|
||||
else:
|
||||
return [str(body)]
|
||||
body = str(body)
|
||||
if isinstance(body, unicodeT):
|
||||
body = to_bytes(body)
|
||||
return [body]
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
@@ -148,7 +155,7 @@ class HTTP(Exception):
|
||||
web2py_error=self.headers.get('web2py_error'))
|
||||
|
||||
def __str__(self):
|
||||
"stringify me"
|
||||
"""stringify me"""
|
||||
return self.message
|
||||
|
||||
|
||||
|
||||
@@ -78,13 +78,9 @@ alert_dependency = ['hashlib', 'uuid']
|
||||
# Now we remove the blacklisted modules if we are using the stated
|
||||
# python version.
|
||||
#
|
||||
# List of modules deprecated in Python 2.6 or 2.7 that are in the above set
|
||||
# List of modules deprecated in Python 2.7 that are in the above list
|
||||
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
|
||||
|
||||
if python_version >= '2.6':
|
||||
base_modules += ['json', 'multiprocessing']
|
||||
base_modules = list(set(base_modules).difference(set(py26_deprecated)))
|
||||
|
||||
if python_version >= '2.7':
|
||||
base_modules += ['argparse', 'json', 'multiprocessing']
|
||||
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
|
||||
|
||||
@@ -18,10 +18,11 @@ import pkgutil
|
||||
import logging
|
||||
from cgi import escape
|
||||
from threading import RLock
|
||||
from gluon.utf8 import Utf8
|
||||
|
||||
from gluon.utils import local_html_escape
|
||||
|
||||
from gluon._compat import copyreg, PY2, maketrans, iterkeys, unicodeT, to_unicode, to_bytes, iteritems, to_native, pjoin
|
||||
|
||||
from pydal.contrib.portalocker import read_locked, LockedFile
|
||||
|
||||
from gluon.fileutils import listdir
|
||||
@@ -49,8 +50,10 @@ DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word
|
||||
|
||||
if PY2:
|
||||
NUMBERS = (int, long, float)
|
||||
from gluon.utf8 import Utf8
|
||||
else:
|
||||
NUMBERS = (int, float)
|
||||
Utf8 = str
|
||||
|
||||
# pattern to find T(blah blah blah) expressions
|
||||
PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
|
||||
@@ -107,15 +110,17 @@ def markmin(s):
|
||||
|
||||
|
||||
def upper_fun(s):
|
||||
return unicode(s, 'utf-8').upper().encode('utf-8')
|
||||
return to_unicode(s).upper()
|
||||
|
||||
|
||||
def title_fun(s):
|
||||
return unicode(s, 'utf-8').title().encode('utf-8')
|
||||
return to_unicode(s).title()
|
||||
|
||||
|
||||
def cap_fun(s):
|
||||
return unicode(s, 'utf-8').capitalize().encode('utf-8')
|
||||
return to_unicode(s).capitalize()
|
||||
|
||||
|
||||
ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
|
||||
ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
|
||||
|
||||
@@ -426,10 +431,16 @@ class lazyT(object):
|
||||
return str(self) if self.M else local_html_escape(str(self), quote=False)
|
||||
|
||||
def encode(self, *a, **b):
|
||||
return str(self).encode(*a, **b)
|
||||
if PY2 and a[0] != 'utf8':
|
||||
return to_unicode(str(self)).encode(*a, **b)
|
||||
else:
|
||||
return str(self)
|
||||
|
||||
def decode(self, *a, **b):
|
||||
return str(self).decode(*a, **b)
|
||||
if PY2:
|
||||
return str(self).decode(*a, **b)
|
||||
else:
|
||||
return str(self)
|
||||
|
||||
def read(self):
|
||||
return str(self)
|
||||
|
||||
@@ -11,7 +11,8 @@ The gluon wsgi application
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
|
||||
if False:
|
||||
import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
|
||||
import gc
|
||||
|
||||
import os
|
||||
@@ -26,7 +27,7 @@ import random
|
||||
import string
|
||||
|
||||
from gluon._compat import Cookie, urllib2
|
||||
#from thread import allocate_lock
|
||||
# from thread import allocate_lock
|
||||
|
||||
from gluon.fileutils import abspath, write_file
|
||||
from gluon.settings import global_settings
|
||||
@@ -67,14 +68,14 @@ import gluon.messageboxhandler
|
||||
logging.gluon = gluon
|
||||
# so we must restore it! Thanks ozancag
|
||||
import locale
|
||||
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
|
||||
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
|
||||
|
||||
exists = os.path.exists
|
||||
pjoin = os.path.join
|
||||
|
||||
try:
|
||||
logging.config.fileConfig(abspath("logging.conf"))
|
||||
except: # fails on GAE or when logfile is missing
|
||||
except: # fails on GAE or when logfile is missing
|
||||
logging.basicConfig()
|
||||
logger = logging.getLogger("web2py")
|
||||
|
||||
@@ -182,12 +183,13 @@ def serve_controller(request, response, session):
|
||||
response._view_environment.update(page)
|
||||
page = run_view_in(response._view_environment)
|
||||
|
||||
# logic to garbage collect after exec, not always, once every 100 requests
|
||||
global requests
|
||||
requests = ('requests' in globals()) and (requests + 1) % 100 or 0
|
||||
if not requests:
|
||||
gc.collect()
|
||||
# end garbage collection logic
|
||||
if not request.env.web2py_disable_garbage_collect:
|
||||
# logic to garbage collect after exec, not always, once every 100 requests
|
||||
global requests
|
||||
requests = ('requests' in globals()) and (requests + 1) % 100 or 0
|
||||
if not requests:
|
||||
gc.collect()
|
||||
# end garbage collection logic
|
||||
|
||||
# ##################################################
|
||||
# set default headers it not set
|
||||
@@ -230,7 +232,7 @@ class LazyWSGI(object):
|
||||
|
||||
to call third party WSGI applications
|
||||
"""
|
||||
self.response.status = str(status).split(' ', 1)[0]
|
||||
self.response.status = int(str(status).split(' ', 1)[0])
|
||||
self.response.headers = dict(headers)
|
||||
return lambda *args, **kargs: \
|
||||
self.response.write(escape=False, *args, **kargs)
|
||||
@@ -254,6 +256,7 @@ class LazyWSGI(object):
|
||||
return [data]
|
||||
for item in middleware_apps:
|
||||
app = item(app)
|
||||
|
||||
def caller(app):
|
||||
return app(self.environ, self.start_response)
|
||||
return lambda caller=caller, app=app: caller(app)
|
||||
@@ -294,9 +297,9 @@ def wsgibase(environ, responder):
|
||||
response = Response()
|
||||
session = Session()
|
||||
env = request.env
|
||||
#env.web2py_path = global_settings.applications_parent
|
||||
# env.web2py_path = global_settings.applications_parent
|
||||
env.web2py_version = web2py_version
|
||||
#env.update(global_settings)
|
||||
# env.update(global_settings)
|
||||
static_file = False
|
||||
http_response = None
|
||||
try:
|
||||
@@ -325,7 +328,6 @@ def wsgibase(environ, responder):
|
||||
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
|
||||
response.stream(static_file, request=request)
|
||||
|
||||
|
||||
# ##################################################
|
||||
# fill in request items
|
||||
# ##################################################
|
||||
@@ -356,17 +358,15 @@ def wsgibase(environ, responder):
|
||||
cmd_opts = global_settings.cmd_options
|
||||
|
||||
request.update(
|
||||
client = client,
|
||||
folder = abspath('applications', app) + os.sep,
|
||||
ajax = x_req_with == 'xmlhttprequest',
|
||||
cid = env.http_web2py_component_element,
|
||||
is_local = (env.remote_addr in local_hosts and
|
||||
client == env.remote_addr),
|
||||
is_shell = False,
|
||||
is_scheduler = False,
|
||||
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
|
||||
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
||||
or env.https == 'on'
|
||||
client=client,
|
||||
folder=abspath('applications', app) + os.sep,
|
||||
ajax=x_req_with == 'xmlhttprequest',
|
||||
cid=env.http_web2py_component_element,
|
||||
is_local=(env.remote_addr in local_hosts and client == env.remote_addr),
|
||||
is_shell=False,
|
||||
is_scheduler=False,
|
||||
is_https=env.wsgi_url_scheme in HTTPS_SCHEMES or
|
||||
request.env.http_x_forwarded_proto in HTTPS_SCHEMES or env.https == 'on'
|
||||
)
|
||||
request.url = environ['PATH_INFO']
|
||||
|
||||
@@ -390,7 +390,7 @@ def wsgibase(environ, responder):
|
||||
% 'invalid request',
|
||||
web2py_error='invalid application')
|
||||
elif not request.is_local and exists(disabled):
|
||||
five0three = os.path.join(request.folder,'static','503.html')
|
||||
five0three = os.path.join(request.folder, 'static', '503.html')
|
||||
if os.path.exists(five0three):
|
||||
raise HTTP(503, file(five0three, 'r').read())
|
||||
else:
|
||||
@@ -406,7 +406,7 @@ def wsgibase(environ, responder):
|
||||
# get the GET and POST data
|
||||
# ##################################################
|
||||
|
||||
#parse_get_post_vars(request, environ)
|
||||
# parse_get_post_vars(request, environ)
|
||||
|
||||
# ##################################################
|
||||
# expose wsgi hooks for convenience
|
||||
@@ -625,7 +625,7 @@ def appfactory(wsgiapp=wsgibase,
|
||||
raise BaseException("Can't create dir %s" % profiler_dir)
|
||||
filepath = pjoin(profiler_dir, 'wtest')
|
||||
try:
|
||||
filehandle = open( filepath, 'w' )
|
||||
filehandle = open(filepath, 'w')
|
||||
filehandle.close()
|
||||
os.unlink(filepath)
|
||||
except IOError:
|
||||
@@ -746,7 +746,7 @@ class HttpServer(object):
|
||||
sock_list = [ip, port]
|
||||
if not ssl_certificate or not ssl_private_key:
|
||||
logger.info('SSL is off')
|
||||
elif not rocket.ssl:
|
||||
elif not rocket.has_ssl:
|
||||
logger.warning('Python "ssl" module unavailable. SSL is OFF')
|
||||
elif not exists(ssl_certificate):
|
||||
logger.warning('unable to open SSL certificate. SSL is OFF')
|
||||
|
||||
@@ -19,13 +19,14 @@ import sched
|
||||
import re
|
||||
import datetime
|
||||
import platform
|
||||
import gluon.fileutils
|
||||
from functools import reduce
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except:
|
||||
import pickle
|
||||
from gluon.settings import global_settings
|
||||
from gluon import fileutils
|
||||
from gluon._compat import to_bytes
|
||||
from pydal.contrib import portalocker
|
||||
|
||||
logger = logging.getLogger("web2py.cron")
|
||||
@@ -116,7 +117,7 @@ class Token(object):
|
||||
def __init__(self, path):
|
||||
self.path = os.path.join(path, 'cron.master')
|
||||
if not os.path.exists(self.path):
|
||||
fileutils.write_file(self.path, '', 'wb')
|
||||
fileutils.write_file(self.path, to_bytes(''), 'wb')
|
||||
self.master = None
|
||||
self.now = time.time()
|
||||
|
||||
@@ -139,7 +140,7 @@ class Token(object):
|
||||
if portalocker.LOCK_EX is None:
|
||||
logger.warning('WEB2PY CRON: Disabled because no file locking')
|
||||
return None
|
||||
self.master = open(self.path, 'rb+')
|
||||
self.master = fileutils.open_file(self.path, 'rb+')
|
||||
try:
|
||||
ret = None
|
||||
portalocker.lock(self.master, portalocker.LOCK_EX)
|
||||
@@ -167,6 +168,7 @@ class Token(object):
|
||||
"""
|
||||
Writes into cron.master the time when cron job was completed
|
||||
"""
|
||||
ret = self.master.closed
|
||||
if not self.master.closed:
|
||||
portalocker.lock(self.master, portalocker.LOCK_EX)
|
||||
logger.debug('WEB2PY CRON: Releasing cron lock')
|
||||
@@ -177,6 +179,7 @@ class Token(object):
|
||||
pickle.dump((self.now, time.time()), self.master)
|
||||
portalocker.unlock(self.master)
|
||||
self.master.close()
|
||||
return ret
|
||||
|
||||
|
||||
def rangetolist(s, period='min'):
|
||||
@@ -222,8 +225,8 @@ def parsecronline(line):
|
||||
params = line.strip().split(None, 6)
|
||||
if len(params) < 7:
|
||||
return None
|
||||
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4,
|
||||
'fri': 5, 'sat': 6}
|
||||
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3,
|
||||
'thu': 4, 'fri': 5, 'sat': 6}
|
||||
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
|
||||
if not s in [None, '*']:
|
||||
task[id] = []
|
||||
@@ -236,7 +239,7 @@ def parsecronline(line):
|
||||
elif val.isdigit() or val == '-1':
|
||||
task[id].append(int(val))
|
||||
elif id == 'dow' and val[:3].lower() in daysofweek:
|
||||
task[id].append(daysofweek(val[:3].lower()))
|
||||
task[id].append(daysofweek[val[:3].lower()])
|
||||
task['user'] = params[5]
|
||||
task['cmd'] = params[6]
|
||||
return task
|
||||
|
||||
Submodule gluon/packages/dal updated: 4d22bb4f84...f0b2df050a
@@ -10,7 +10,7 @@ Restricted environment to execute application's code
|
||||
"""
|
||||
|
||||
import sys
|
||||
from gluon._compat import pickle, ClassType
|
||||
from gluon._compat import pickle, ClassType, unicodeT, to_bytes
|
||||
import traceback
|
||||
import types
|
||||
import os
|
||||
@@ -137,7 +137,10 @@ class RestrictedError(Exception):
|
||||
self.environment = environment
|
||||
if layer:
|
||||
try:
|
||||
self.traceback = traceback.format_exc()
|
||||
try:
|
||||
self.traceback = traceback.format_exc()
|
||||
except:
|
||||
self.traceback = traceback.format_exc(limit=1)
|
||||
except:
|
||||
self.traceback = 'no traceback because template parsing error'
|
||||
try:
|
||||
@@ -189,10 +192,10 @@ class RestrictedError(Exception):
|
||||
# safely show an useful message to the user
|
||||
try:
|
||||
output = self.output
|
||||
if isinstance(output, unicode):
|
||||
output = output.encode("utf8")
|
||||
elif not isinstance(output, str):
|
||||
if not isinstance(output, str, bytes, bytearray):
|
||||
output = str(output)
|
||||
if isinstance(output, unicodeT):
|
||||
output = to_bytes(output)
|
||||
except:
|
||||
output = ""
|
||||
return output
|
||||
@@ -202,7 +205,7 @@ def compile2(code, layer):
|
||||
return compile(code, layer, 'exec')
|
||||
|
||||
|
||||
def restricted(ccode, environment=None, layer='Unknown'):
|
||||
def restricted(ccode, environment=None, layer='Unknown', scode=None):
|
||||
"""
|
||||
Runs code in environment and returns the output. If an exception occurs
|
||||
in code it raises a RestrictedError containing the traceback. Layer is
|
||||
@@ -227,7 +230,9 @@ def restricted(ccode, environment=None, layer='Unknown'):
|
||||
sys.excepthook(etype, evalue, tb)
|
||||
del tb
|
||||
output = "%s %s" % (etype, evalue)
|
||||
raise RestrictedError(layer, ccode, output, environment)
|
||||
# Save source code in ticket when available
|
||||
scode = scode if scode else ccode
|
||||
raise RestrictedError(layer, scode, output, environment)
|
||||
|
||||
|
||||
def snapshot(info=None, context=5, code=None, environment=None):
|
||||
|
||||
@@ -22,12 +22,11 @@ import re
|
||||
import logging
|
||||
import traceback
|
||||
import threading
|
||||
import urllib
|
||||
from gluon.storage import Storage, List
|
||||
from gluon.http import HTTP
|
||||
from gluon.fileutils import abspath, read_file
|
||||
from gluon.settings import global_settings
|
||||
from gluon._compat import urllib_unquote, urllib_quote, iteritems, xrange
|
||||
from gluon._compat import urllib_unquote, urllib_quote, iteritems, xrange, urllib_quote_plus
|
||||
|
||||
isdir = os.path.isdir
|
||||
isfile = os.path.isfile
|
||||
@@ -206,8 +205,7 @@ def url_out(request, environ, application, controller, function,
|
||||
if host is True or (host is None and (scheme or port is not None)):
|
||||
host = request.env.http_host
|
||||
if not scheme or scheme is True:
|
||||
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
|
||||
if request else 'http'
|
||||
scheme = request.env.get('wsgi_url_scheme', 'http').lower() if request else 'http'
|
||||
if host:
|
||||
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
|
||||
url = '%s://%s%s' % (scheme, host_port, url)
|
||||
@@ -236,7 +234,7 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
|
||||
path_info, query_string = uri, ''
|
||||
query_string += \
|
||||
'code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
||||
(status, ticket, urllib.quote_plus(
|
||||
(status, ticket, urllib_quote_plus(
|
||||
request.env.request_uri), request.url)
|
||||
if uri.startswith('http://') or uri.startswith('https://'):
|
||||
# make up a response
|
||||
@@ -271,12 +269,12 @@ def try_redirect_on_error(http_object, request, ticket=None):
|
||||
elif '?' in redir:
|
||||
url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
||||
(redir, status, ticket,
|
||||
urllib.quote_plus(request.env.request_uri),
|
||||
urllib_quote_plus(request.env.request_uri),
|
||||
request.url)
|
||||
else:
|
||||
url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
|
||||
(redir, status, ticket,
|
||||
urllib.quote_plus(request.env.request_uri),
|
||||
urllib_quote_plus(request.env.request_uri),
|
||||
request.url)
|
||||
return HTTP(303, 'You are being redirected <a href="%s">here</a>' % url, Location=url)
|
||||
return http_object
|
||||
@@ -317,7 +315,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
|
||||
|
||||
symbols = dict(app=app)
|
||||
try:
|
||||
exec(data + '\n', symbols)
|
||||
exec(data, symbols)
|
||||
except SyntaxError as e:
|
||||
logger.error(
|
||||
'%s has a syntax error and will not be loaded\n' % path
|
||||
|
||||
@@ -307,13 +307,13 @@ try:
|
||||
except ImportError:
|
||||
has_futures = False
|
||||
|
||||
class Future:
|
||||
class Future(object):
|
||||
pass
|
||||
|
||||
class ThreadPoolExecutor:
|
||||
class ThreadPoolExecutor(object):
|
||||
pass
|
||||
|
||||
class _WorkItem:
|
||||
class _WorkItem(object):
|
||||
pass
|
||||
|
||||
|
||||
@@ -784,8 +784,7 @@ class Rocket(object):
|
||||
the application developer. Please update your \
|
||||
applications to no longer call rocket.stop(True)"
|
||||
try:
|
||||
import warnings
|
||||
raise warnings.DeprecationWarning(msg)
|
||||
raise DeprecationWarning(msg)
|
||||
except ImportError:
|
||||
raise RuntimeError(msg)
|
||||
|
||||
@@ -1751,7 +1750,7 @@ class WSGIWorker(Worker):
|
||||
if self.request_method != 'HEAD':
|
||||
try:
|
||||
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:
|
||||
self.conn.sendall(to_bytes(data))
|
||||
except socket.timeout:
|
||||
|
||||
@@ -11,7 +11,7 @@ Cross-site scripting (XSS) defense
|
||||
"""
|
||||
|
||||
from gluon._compat import HTMLParser, urlparse, entitydefs, basestring
|
||||
from cgi import escape
|
||||
from gluon.utils import local_html_escape
|
||||
from formatter import AbstractFormatter
|
||||
from xml.sax.saxutils import quoteattr
|
||||
|
||||
@@ -21,7 +21,7 @@ __all__ = ['sanitize']
|
||||
def xssescape(text):
|
||||
"""Gets rid of < and > and & and, for good measure, :"""
|
||||
|
||||
return escape(text, quote=True).replace(':', ':')
|
||||
return local_html_escape(text, quote=True).replace(':', ':')
|
||||
|
||||
|
||||
class XssCleaner(HTMLParser):
|
||||
@@ -145,7 +145,7 @@ class XssCleaner(HTMLParser):
|
||||
if url.startswith('#'):
|
||||
return True
|
||||
else:
|
||||
parsed = urlparse(url)
|
||||
parsed = urlparse.urlparse(url)
|
||||
return ((parsed[0] in self.allowed_schemes and '.' in parsed[1]) or
|
||||
(parsed[0] in self.allowed_schemes and '@' in parsed[2]) or
|
||||
(parsed[0] == '' and parsed[2].startswith('/')))
|
||||
|
||||
@@ -525,7 +525,7 @@ class MetaScheduler(threading.Thread):
|
||||
self.have_heartbeat = True # set to False to kill
|
||||
self.empty_runs = 0
|
||||
|
||||
def async(self, task):
|
||||
def local_async(self, task):
|
||||
"""Start the background process.
|
||||
|
||||
Args:
|
||||
@@ -913,7 +913,7 @@ class Scheduler(MetaScheduler):
|
||||
self.w_stats.empty_runs = 0
|
||||
self.w_stats.status = RUNNING
|
||||
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:
|
||||
self.w_stats.status = ACTIVE
|
||||
else:
|
||||
@@ -1158,7 +1158,7 @@ class Scheduler(MetaScheduler):
|
||||
if not self.db_thread:
|
||||
logger.debug('thread building own DAL object')
|
||||
self.db_thread = DAL(
|
||||
self.db._uri, folder=self.db._adapter.folder)
|
||||
self.db._uri, folder=self.db._adapter.folder, decode_credentials=True)
|
||||
self.define_tables(self.db_thread, migrate=False)
|
||||
try:
|
||||
db = self.db_thread
|
||||
@@ -1698,7 +1698,7 @@ def main():
|
||||
print('groups for this worker: ' + ', '.join(group_names))
|
||||
print('connecting to database in folder: ' + options.db_folder or './')
|
||||
print('using URI: ' + options.db_uri)
|
||||
db = DAL(options.db_uri, folder=options.db_folder)
|
||||
db = DAL(options.db_uri, folder=options.db_folder, decode_credentials=True)
|
||||
print('instantiating scheduler...')
|
||||
scheduler = Scheduler(db=db,
|
||||
worker_name=options.worker_name,
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
def json(value, default=custom_json, indent=None):
|
||||
value = json_parser.dumps(value, default=default, sort_keys=True, indent=indent)
|
||||
def json(value, default=custom_json, indent=None, sort_keys=False):
|
||||
value = json_parser.dumps(value, default=default, sort_keys=sort_keys, indent=indent)
|
||||
# replace JavaScript incompatible spacing
|
||||
# http://timelessrepo.com/json-isnt-a-javascript-subset
|
||||
# PY3 FIXME
|
||||
|
||||
@@ -31,10 +31,16 @@ from gluon.globals import Request, Response, Session
|
||||
from gluon.storage import Storage, List
|
||||
from gluon.admin import w2p_unpack
|
||||
from pydal.base import BaseAdapter
|
||||
from gluon._compat import iteritems, ClassType
|
||||
from gluon._compat import iteritems, ClassType, PY2
|
||||
|
||||
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):
|
||||
try:
|
||||
|
||||
230
gluon/sqlhtml.py
230
gluon/sqlhtml.py
@@ -15,11 +15,11 @@ Holds:
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import urllib
|
||||
import re
|
||||
import copy
|
||||
|
||||
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.html import XmlComponent, truncate_string
|
||||
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
|
||||
@@ -29,7 +29,7 @@ from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
|
||||
from pydal.base import DEFAULT
|
||||
from pydal.objects import Table, Row, Expression, Field, Set, Rows
|
||||
from pydal.adapters.base import CALLABLETYPES
|
||||
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
|
||||
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref, merge_tablemaps
|
||||
from pydal.helpers.classes import Reference, SQLCustomType
|
||||
from gluon.storage import Storage
|
||||
from gluon.utils import md5_hash
|
||||
@@ -405,7 +405,7 @@ class RadioWidget(OptionsWidget):
|
||||
cols = attributes.get('cols', 1)
|
||||
totals = len(options)
|
||||
mods = totals % cols
|
||||
rows = totals / cols
|
||||
rows = totals // cols
|
||||
if mods:
|
||||
rows += 1
|
||||
|
||||
@@ -471,7 +471,7 @@ class CheckboxesWidget(OptionsWidget):
|
||||
cols = attributes.get('cols', 1)
|
||||
totals = len(options)
|
||||
mods = totals % cols
|
||||
rows = totals / cols
|
||||
rows = totals // cols
|
||||
if mods:
|
||||
rows += 1
|
||||
|
||||
@@ -658,7 +658,8 @@ class AutocompleteWidget(object):
|
||||
orderby=None, limitby=(0, 10), distinct=False,
|
||||
keyword='_autocomplete_%(tablename)s_%(fieldname)s',
|
||||
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_string = help_string
|
||||
@@ -681,12 +682,14 @@ class AutocompleteWidget(object):
|
||||
else:
|
||||
self.is_reference = False
|
||||
if hasattr(request, 'application'):
|
||||
urlvars = request.vars
|
||||
urlvars = copy.copy(request.vars)
|
||||
urlvars[default_var] = 1
|
||||
self.url = URL(args=request.args, vars=urlvars)
|
||||
self.callback()
|
||||
self.url = URL(args=request.args, vars=urlvars,
|
||||
user_signature=user_signature, hash_vars=hash_vars)
|
||||
self.run_callback = True
|
||||
else:
|
||||
self.url = request
|
||||
self.run_callback = False
|
||||
|
||||
def callback(self):
|
||||
if self.keyword in self.request.vars:
|
||||
@@ -759,6 +762,8 @@ class AutocompleteWidget(object):
|
||||
raise HTTP(200, '')
|
||||
|
||||
def __call__(self, field, value, **attributes):
|
||||
if self.run_callback:
|
||||
self.callback()
|
||||
default = dict(
|
||||
_type='text',
|
||||
value=(value is not None and str(value)) or '',
|
||||
@@ -1082,6 +1087,106 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
|
||||
return parent
|
||||
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):
|
||||
|
||||
@@ -1170,6 +1275,8 @@ class SQLFORM(FORM):
|
||||
bootstrap=formstyle_bootstrap,
|
||||
bootstrap3_stacked=formstyle_bootstrap3_stacked,
|
||||
bootstrap3_inline=formstyle_bootstrap3_inline_factory(3),
|
||||
bootstrap4_stacked=formstyle_bootstrap4_stacked,
|
||||
bootstrap4_inline=formstyle_bootstrap4_inline_factory(3),
|
||||
inline=formstyle_inline,
|
||||
)
|
||||
|
||||
@@ -1237,9 +1344,16 @@ class SQLFORM(FORM):
|
||||
# if no fields are provided, build it from the provided table
|
||||
# will only use writable or readable fields, unless forced to ignore
|
||||
if fields is None:
|
||||
fields = [f.name for f in table if
|
||||
(ignore_rw or f.writable or f.readable) and
|
||||
(readonly or not f.compute)]
|
||||
if not readonly:
|
||||
if not record:
|
||||
# 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
|
||||
|
||||
# make sure we have an id
|
||||
@@ -1322,7 +1436,7 @@ class SQLFORM(FORM):
|
||||
label = LABEL(label, label and sep, _for=field_id,
|
||||
_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)
|
||||
|
||||
if cond:
|
||||
@@ -1568,6 +1682,7 @@ class SQLFORM(FORM):
|
||||
keepvalues = True if self.record else False
|
||||
|
||||
if self.readonly:
|
||||
self.deleted = False
|
||||
return False
|
||||
|
||||
if request_vars.__class__.__name__ == 'Request':
|
||||
@@ -1724,7 +1839,7 @@ class SQLFORM(FORM):
|
||||
continue
|
||||
|
||||
field = self.table[fieldname]
|
||||
if field.type == 'id':
|
||||
if field.type == 'id' or field.compute:
|
||||
continue
|
||||
if field.type == 'boolean':
|
||||
if self.vars.get(fieldname, False):
|
||||
@@ -1916,8 +2031,10 @@ class SQLFORM(FORM):
|
||||
if 'table_name' in attributes:
|
||||
del attributes['table_name']
|
||||
|
||||
return SQLFORM(DAL(None).define_table(table_name, *fields),
|
||||
**attributes)
|
||||
# Clone fields, while passing tables straight through
|
||||
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
|
||||
def build_query(fields, keywords):
|
||||
@@ -1932,11 +2049,13 @@ class SQLFORM(FORM):
|
||||
if settings.global_settings.web2py_runtime_gae:
|
||||
return reduce(lambda a,b: a|b, [field.contains(key) for field in sfields])
|
||||
else:
|
||||
if not (sfields and key and key.split()):
|
||||
return fields[0].table
|
||||
return reduce(lambda a,b:a&b,[
|
||||
reduce(lambda a,b: a|b, [
|
||||
field.contains(k) for field in sfields]
|
||||
) for k in key.split()])
|
||||
|
||||
|
||||
# from https://groups.google.com/forum/#!topic/web2py/hKe6lI25Bv4
|
||||
# needs testing...
|
||||
#words = key.split(' ') if key else []
|
||||
@@ -2156,6 +2275,7 @@ class SQLFORM(FORM):
|
||||
represent_none=None,
|
||||
showblobs=False):
|
||||
|
||||
dbset = None
|
||||
formstyle = formstyle or current.response.formstyle
|
||||
if isinstance(query, Set):
|
||||
query = query.query
|
||||
@@ -2187,7 +2307,7 @@ class SQLFORM(FORM):
|
||||
cornerall='',
|
||||
cornertop='',
|
||||
cornerbottom='',
|
||||
button='button btn btn-default',
|
||||
button='button btn btn-default btn-secondary',
|
||||
buttontext='buttontext button',
|
||||
buttonadd='icon plus icon-plus glyphicon glyphicon-plus',
|
||||
buttonback='icon leftarrow icon-arrow-left glyphicon glyphicon-arrow-left',
|
||||
@@ -2329,7 +2449,7 @@ class SQLFORM(FORM):
|
||||
if not isinstance(left, (list, tuple)):
|
||||
left = [left]
|
||||
for join in left:
|
||||
tablenames += db._adapter.tables(join)
|
||||
tablenames = merge_tablemaps(tablenames, db._adapter.tables(join))
|
||||
tables = [db[tablename] for tablename in tablenames]
|
||||
if fields:
|
||||
# add missing tablename to virtual fields
|
||||
@@ -2337,14 +2457,15 @@ class SQLFORM(FORM):
|
||||
for k, f in iteritems(table):
|
||||
if isinstance(f, Field.Virtual):
|
||||
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:
|
||||
fields = []
|
||||
columns = []
|
||||
filter1 = lambda f: isinstance(f, Field) and f.readable 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 and f.listable
|
||||
for table in tables:
|
||||
fields += filter(filter1, table)
|
||||
columns += filter(filter1, table)
|
||||
columns += filter(filter2, table)
|
||||
for k, f in iteritems(table):
|
||||
if not k.startswith('_'):
|
||||
if isinstance(f, Field.Virtual) and f.readable:
|
||||
@@ -2553,13 +2674,14 @@ class SQLFORM(FORM):
|
||||
if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
|
||||
expcolumns.append(str(field))
|
||||
|
||||
expcolumns = ['"%s"' % '"."'.join(f.split('.')) for f in expcolumns]
|
||||
if export_type in exportManager and exportManager[export_type]:
|
||||
if keywords:
|
||||
try:
|
||||
# the query should be constructed using searchable
|
||||
# fields but not virtual fields
|
||||
sfields = reduce(lambda a, b: a + b,
|
||||
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
|
||||
is_searchable = lambda f: f.readable and not isinstance(f, Field.Virtual) and f.searchable
|
||||
sfields = reduce(lambda a, b: a + b, [filter(is_searchable, t) for t in tables])
|
||||
# use custom_query using searchable
|
||||
if callable(searchable):
|
||||
dbset = dbset(searchable(sfields, keywords))
|
||||
@@ -2626,8 +2748,8 @@ class SQLFORM(FORM):
|
||||
_id=skeywords_id, _class='form-control',
|
||||
_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('Clear'), _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 btn-secondary",
|
||||
_onclick="jQuery('#%s').val('');" % skeywords_id),
|
||||
*hidden_fields,
|
||||
_method="GET", _action=url), search_menu)
|
||||
@@ -3013,7 +3135,7 @@ class SQLFORM(FORM):
|
||||
order=request.vars.order or '',
|
||||
_export_type=k,
|
||||
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 = \
|
||||
DIV(T('Export:'), _class="w2p_export_menu", *export_links)
|
||||
else:
|
||||
@@ -3032,6 +3154,7 @@ class SQLFORM(FORM):
|
||||
res.view_form = view_form
|
||||
res.search_form = search_form
|
||||
res.rows = rows
|
||||
res.dbset = dbset
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
@@ -3150,7 +3273,9 @@ class SQLFORM(FORM):
|
||||
# if isinstance(linked_tables, dict):
|
||||
# linked_tables = linked_tables.get(table._tablename, [])
|
||||
if linked_tables is None or referee in linked_tables:
|
||||
field.represent = lambda id, r=None, referee=referee, rep=field.represent: A(callable(rep) and rep(id) or id, cid=request.cid, _href=url(args=['view', referee, id]))
|
||||
field.represent = (lambda id, r=None, referee=referee, rep=field.represent:
|
||||
A(callable(rep) and rep(id) or id,
|
||||
cid=request.cid, _href=url(args=['view', referee, id])))
|
||||
except (KeyError, ValueError, TypeError):
|
||||
redirect(URL(args=table._tablename))
|
||||
if nargs == len(args) + 1:
|
||||
@@ -3311,23 +3436,28 @@ class SQLTABLE(TABLE):
|
||||
if not sqlrows:
|
||||
return
|
||||
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
|
||||
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields))
|
||||
tablemap = dict(((f.tablename, f.table) if isinstance(f, Field) else (f._table._tablename, f._table) for f in fieldmap.values()))
|
||||
for table in tablemap.values():
|
||||
pref = table._tablename + '.'
|
||||
fieldmap.update(((pref+f.name, f) for f in table._virtual_fields))
|
||||
fieldmap.update(((pref+f.name, f) for f in table._virtual_methods))
|
||||
field_types = (Field, Field.Virtual, Field.Method)
|
||||
if not columns:
|
||||
columns = list(sqlrows.colnames)
|
||||
if headers == 'fieldname:capitalize':
|
||||
header_func = {
|
||||
'fieldname:capitalize': lambda f: f.name.replace('_', ' ').title(),
|
||||
'labels': lambda f: f.label
|
||||
}
|
||||
if isinstance(headers, str) and headers in header_func:
|
||||
make_name = header_func[headers]
|
||||
headers = {}
|
||||
for c in columns:
|
||||
tfmatch = REGEX_TABLE_DOT_FIELD.match(c)
|
||||
if tfmatch:
|
||||
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups()
|
||||
headers[t + '.' + f] = f.replace('_', ' ').title()
|
||||
f = fieldmap.get(c)
|
||||
if isinstance(f, field_types):
|
||||
headers[c] = make_name(f)
|
||||
else:
|
||||
headers[c] = REGEX_ALIAS_MATCH.sub(r'\2', c)
|
||||
elif headers == 'labels':
|
||||
headers = {}
|
||||
for c in columns:
|
||||
(t, f) = c.split('.')
|
||||
field = sqlrows.db[t][f]
|
||||
headers[c] = field.label
|
||||
if colgroup:
|
||||
cols = [COL(_id=c.replace('.', '-'), data={'column': i + 1})
|
||||
for i, c in enumerate(columns)]
|
||||
@@ -3380,9 +3510,8 @@ class SQLTABLE(TABLE):
|
||||
_class += ' rowselected'
|
||||
|
||||
for colname in columns:
|
||||
matched_column_field = \
|
||||
sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD.match(colname)
|
||||
if not matched_column_field:
|
||||
field = fieldmap.get(colname)
|
||||
if not isinstance(field, field_types):
|
||||
if "_extra" in record and colname in record._extra:
|
||||
r = record._extra[colname]
|
||||
row.append(TD(r))
|
||||
@@ -3390,12 +3519,9 @@ class SQLTABLE(TABLE):
|
||||
else:
|
||||
raise KeyError(
|
||||
"Column %s not found (SQLTABLE)" % colname)
|
||||
(tablename, fieldname) = matched_column_field.groups()
|
||||
colname = tablename + '.' + fieldname
|
||||
try:
|
||||
field = sqlrows.db[tablename][fieldname]
|
||||
except (KeyError, AttributeError):
|
||||
field = None
|
||||
# Virtual fields don't have parent table name...
|
||||
tablename = colname.split('.', 1)[0]
|
||||
fieldname = field.name
|
||||
if tablename in record \
|
||||
and isinstance(record, Row) \
|
||||
and isinstance(record[tablename], Row):
|
||||
@@ -3423,7 +3549,7 @@ class SQLTABLE(TABLE):
|
||||
if ref.find('.') >= 0:
|
||||
tref, fref = ref.split('.')
|
||||
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))
|
||||
elif field.represent:
|
||||
if field not in repr_cache:
|
||||
@@ -3434,7 +3560,7 @@ class SQLTABLE(TABLE):
|
||||
elif linkto and hasattr(field._table, '_primarykey')\
|
||||
and fieldname in field._table._primarykey:
|
||||
# have to test this with multi-key tables
|
||||
key = urllib.urlencode(dict([
|
||||
key = urlencode(dict([
|
||||
((tablename in record
|
||||
and isinstance(record, Row)
|
||||
and isinstance(record[tablename], Row)) and
|
||||
@@ -3554,7 +3680,9 @@ class ExportClass(object):
|
||||
if not self.rows.db._adapter.REGEX_TABLE_DOT_FIELD.match(col):
|
||||
row.append(record._extra[col])
|
||||
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]
|
||||
if isinstance(record.get(t, None), (Row, dict)):
|
||||
value = record[t][f]
|
||||
|
||||
@@ -13,6 +13,7 @@ from .test_contribs import *
|
||||
from .test_routes import *
|
||||
from .test_router import *
|
||||
from .test_validators import *
|
||||
from .test_authapi import *
|
||||
from .test_tools import *
|
||||
from .test_utils import *
|
||||
from .test_serializers import *
|
||||
@@ -22,7 +23,8 @@ from .test_appadmin import *
|
||||
from .test_web import *
|
||||
from .test_sqlhtml import *
|
||||
from .test_scheduler import *
|
||||
from .test_cron import *
|
||||
from .test_is_url import *
|
||||
|
||||
if sys.version[:3] == '2.7':
|
||||
from .test_is_url import *
|
||||
from .test_old_doctests import *
|
||||
|
||||
@@ -17,6 +17,7 @@ from gluon import fileutils
|
||||
from gluon.dal import DAL, Field, Table
|
||||
from gluon.http import HTTP
|
||||
from gluon.fileutils import open_file
|
||||
from gluon.cache import CacheInRam
|
||||
|
||||
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||
|
||||
@@ -104,6 +105,21 @@ class TestAppAdmin(unittest.TestCase):
|
||||
self._test_index()
|
||||
remove_compiled_application(appname_path)
|
||||
|
||||
def test_index_minify(self):
|
||||
# test for gluon/contrib/minify
|
||||
self.env['response'].optimize_css = 'concat|minify'
|
||||
self.env['response'].optimize_js = 'concat|minify'
|
||||
self.env['current'].cache = Storage({'ram':CacheInRam()})
|
||||
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
|
||||
self._test_index()
|
||||
file_l = os.listdir(os.path.join(appname_path, 'static', 'temp'))
|
||||
file_l.sort()
|
||||
self.assertTrue(len(file_l) == 2)
|
||||
self.assertEqual(file_l[0][0:10], 'compressed')
|
||||
self.assertEqual(file_l[1][0:10], 'compressed')
|
||||
self.assertEqual(file_l[0][-3:], 'css')
|
||||
self.assertEqual(file_l[1][-2:], 'js')
|
||||
|
||||
def test_select(self):
|
||||
request = self.env['request']
|
||||
request.args = List(['db'])
|
||||
|
||||
174
gluon/tests/test_authapi.py
Normal file
174
gluon/tests/test_authapi.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Unit tests for authapi """
|
||||
import os
|
||||
import unittest
|
||||
from gluon.globals import Request, Response, Session
|
||||
from gluon.languages import translator
|
||||
from gluon.dal import DAL, Field
|
||||
from gluon.authapi import AuthAPI
|
||||
from gluon.storage import Storage
|
||||
from gluon._compat import to_bytes, to_native, add_charset
|
||||
|
||||
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||
|
||||
|
||||
class TestAuthAPI(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request = Request(env={})
|
||||
self.request.application = 'a'
|
||||
self.request.controller = 'c'
|
||||
self.request.function = 'f'
|
||||
self.request.folder = 'applications/admin'
|
||||
self.response = Response()
|
||||
self.session = Session()
|
||||
T = translator('', 'en')
|
||||
self.session.connect(self.request, self.response)
|
||||
from gluon.globals import current
|
||||
self.current = current
|
||||
self.current.request = self.request
|
||||
self.current.response = self.response
|
||||
self.current.session = self.session
|
||||
self.current.T = T
|
||||
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
|
||||
self.auth = AuthAPI(self.db)
|
||||
self.auth.define_tables(username=True, signature=False)
|
||||
# Create a user
|
||||
self.auth.table_user().validate_and_insert(first_name='Bart',
|
||||
last_name='Simpson',
|
||||
username='bart',
|
||||
email='bart@simpson.com',
|
||||
password='bart_password',
|
||||
registration_key='',
|
||||
registration_id=''
|
||||
)
|
||||
self.db.commit()
|
||||
|
||||
def test_login(self):
|
||||
result = self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
self.assertTrue(result['user']['email'] == 'bart@simpson.com')
|
||||
self.auth.logout()
|
||||
self.assertFalse(self.auth.is_logged_in())
|
||||
self.auth.settings.username_case_sensitive = False
|
||||
result = self.auth.login(**{'username': 'BarT', 'password': 'bart_password'})
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
|
||||
def test_logout(self):
|
||||
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
result = self.auth.logout()
|
||||
self.assertTrue(not self.auth.is_logged_in())
|
||||
self.assertTrue(result['user'] is None)
|
||||
|
||||
def test_register(self):
|
||||
self.auth.settings.login_after_registration = True
|
||||
result = self.auth.register(**{
|
||||
'username': 'lisa',
|
||||
'first_name': 'Lisa',
|
||||
'last_name': 'Simpson',
|
||||
'email': 'lisa@simpson.com',
|
||||
'password': 'lisa_password'
|
||||
})
|
||||
self.assertTrue(result['user']['email'] == 'lisa@simpson.com')
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
with self.assertRaises(AssertionError): # Can't register if you're logged in
|
||||
result = self.auth.register(**{
|
||||
'username': 'lisa',
|
||||
'first_name': 'Lisa',
|
||||
'last_name': 'Simpson',
|
||||
'email': 'lisa@simpson.com',
|
||||
'password': 'lisa_password'
|
||||
})
|
||||
self.auth.logout()
|
||||
self.auth.settings.login_after_registration = False
|
||||
result = self.auth.register(**{
|
||||
'username': 'barney',
|
||||
'first_name': 'Barney',
|
||||
'last_name': 'Gumble',
|
||||
'email': 'barney@simpson.com',
|
||||
'password': 'barney_password'
|
||||
})
|
||||
self.assertTrue(result['user']['email'] == 'barney@simpson.com')
|
||||
self.assertFalse(self.auth.is_logged_in())
|
||||
self.auth.settings.login_userfield = 'email'
|
||||
result = self.auth.register(**{
|
||||
'username': 'lisa',
|
||||
'first_name': 'Lisa',
|
||||
'last_name': 'Simpson',
|
||||
'email': 'lisa@simpson.com',
|
||||
'password': 'lisa_password'
|
||||
})
|
||||
self.assertTrue(result['errors']['email'] == self.auth.messages.email_taken)
|
||||
self.assertTrue(result['user'] is None)
|
||||
self.auth.settings.registration_requires_verification = True
|
||||
result = self.auth.register(**{
|
||||
'username': 'homer',
|
||||
'first_name': 'Homer',
|
||||
'last_name': 'Simpson',
|
||||
'email': 'homer@simpson.com',
|
||||
'password': 'homer_password'
|
||||
})
|
||||
self.assertTrue('key' in result['user'])
|
||||
|
||||
def test_profile(self):
|
||||
with self.assertRaises(AssertionError):
|
||||
# We are not logged in
|
||||
self.auth.profile()
|
||||
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
result = self.auth.profile(email='bartolo@simpson.com')
|
||||
self.assertTrue(result['user']['email'] == 'bartolo@simpson.com')
|
||||
self.assertTrue(self.auth.table_user()[result['user']['id']].email == 'bartolo@simpson.com')
|
||||
|
||||
def test_change_password(self):
|
||||
with self.assertRaises(AssertionError):
|
||||
# We are not logged in
|
||||
self.auth.change_password()
|
||||
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
|
||||
self.auth.logout()
|
||||
self.assertTrue(not self.auth.is_logged_in())
|
||||
self.auth.login(username='bart', password='1234')
|
||||
self.assertTrue(self.auth.is_logged_in())
|
||||
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='5678')
|
||||
self.assertTrue('new_password2' in result['errors'])
|
||||
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
|
||||
self.assertTrue('old_password' in result['errors'])
|
||||
# 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):
|
||||
self.auth.settings.registration_requires_verification = True
|
||||
result = self.auth.register(**{
|
||||
'username': 'homer',
|
||||
'first_name': 'Homer',
|
||||
'last_name': 'Simpson',
|
||||
'email': 'homer@simpson.com',
|
||||
'password': 'homer_password'
|
||||
})
|
||||
self.assertTrue('key' in result['user'])
|
||||
homer_id = result['user']['id']
|
||||
homers_key = result['user']['key']
|
||||
result = self.auth.verify_key(key=None)
|
||||
self.assertTrue(result['errors'] is not None)
|
||||
result = self.auth.verify_key(key='12345')
|
||||
self.assertTrue(result['errors'] is not None)
|
||||
result = self.auth.verify_key(key=homers_key)
|
||||
self.assertTrue(result['errors'] is None)
|
||||
self.assertEqual(self.auth.table_user()[homer_id].registration_key, '')
|
||||
self.auth.settings.registration_requires_approval = True
|
||||
result = self.auth.register(**{
|
||||
'username': 'lisa',
|
||||
'first_name': 'Lisa',
|
||||
'last_name': 'Simpson',
|
||||
'email': 'lisa@simpson.com',
|
||||
'password': 'lisa_password'
|
||||
})
|
||||
lisa_id = result['user']['id']
|
||||
result = self.auth.verify_key(key=result['user']['key'])
|
||||
self.assertEqual(self.auth.table_user()[lisa_id].registration_key, 'pending')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user