Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
baa129f871 | ||
|
|
90e606dcfd | ||
|
|
1d77968a06 | ||
|
|
1842a2e42c | ||
|
|
c9b6b0faf8 | ||
|
|
d8be963656 | ||
|
|
496112c4fe | ||
|
|
a186f5c51f | ||
|
|
fef43cd053 | ||
|
|
ab41cd94ec | ||
|
|
9ab7ed0029 | ||
|
|
4d117af85f | ||
|
|
60a6180a77 | ||
|
|
ab537242c4 | ||
|
|
02d2fefc21 | ||
|
|
232598fd8d | ||
|
|
7a69e087f7 | ||
|
|
6da3a9d8fd | ||
|
|
fe0f506efc | ||
|
|
140023e920 | ||
|
|
ad43249f61 | ||
|
|
dd31fb480c | ||
|
|
e5121876db | ||
|
|
532137bce5 | ||
|
|
35f7caa2f0 | ||
|
|
98dddee697 | ||
|
|
5000c47472 | ||
|
|
bf3d53ad96 | ||
|
|
b2f221bbaa | ||
|
|
868d6f6369 | ||
|
|
0e1831bcc7 | ||
|
|
dda808ebda | ||
|
|
f31c9002f7 | ||
|
|
6354fbedf8 | ||
|
|
cc11a14ce3 | ||
|
|
2fa9597cd8 | ||
|
|
c9d656ea45 | ||
|
|
1eec01b830 | ||
|
|
c10983b191 | ||
|
|
975ab7b923 | ||
|
|
a81e116274 | ||
|
|
34f2825a49 | ||
|
|
7dec909254 | ||
|
|
ca9198a26e | ||
|
|
ad421e42c6 | ||
|
|
6954988851 | ||
|
|
f0382d646c | ||
|
|
48a0683dd1 | ||
|
|
d7fb270e7e | ||
|
|
86a2c529b9 | ||
|
|
55592e7c6e | ||
|
|
16f60d5cff | ||
|
|
930370ee91 | ||
|
|
215abc9e4f | ||
|
|
b6e5e16526 | ||
|
|
b2548f5631 | ||
|
|
f14c384d83 | ||
|
|
9f186e5d5d | ||
|
|
51dd427b9e | ||
|
|
7b3fe560ed | ||
|
|
bd526452a8 | ||
|
|
d862da3543 | ||
|
|
0165cfadef | ||
|
|
46b7716b1d | ||
|
|
7e5277030a | ||
|
|
b2e03c9dee | ||
|
|
0644df5283 | ||
|
|
4acd9f8f2b | ||
|
|
ce0c5f2d5a | ||
|
|
28b0385d9b | ||
|
|
ac9bccb9a2 | ||
|
|
aa5b40528f | ||
|
|
96efc7bfce | ||
|
|
aa584faed6 | ||
|
|
e7cab3b975 | ||
|
|
867f93b634 | ||
|
|
efff27ffe4 | ||
|
|
637579f531 | ||
|
|
52fe4407b8 | ||
|
|
fc0add67b5 | ||
|
|
3d2834c81a | ||
|
|
a23b264a37 | ||
|
|
0b8ecea4dc | ||
|
|
79e256b1d7 | ||
|
|
67945c2d5c | ||
|
|
c600854f4b | ||
|
|
920ab72415 | ||
|
|
757d46274e | ||
|
|
7b66ec0ae3 | ||
|
|
bf5ec0d7cf | ||
|
|
2c70a858f1 | ||
|
|
ad9ebea900 | ||
|
|
c72330f2cd | ||
|
|
091d9c74b0 | ||
|
|
bebdbd9d5e | ||
|
|
05e28ddffd | ||
|
|
e19435dbcf | ||
|
|
90ee6f3754 | ||
|
|
d51ea90e18 | ||
|
|
3fed558bdd | ||
|
|
870d9d3e57 | ||
|
|
0a07af55f9 | ||
|
|
f7adfbde76 | ||
|
|
75c1d80824 | ||
|
|
c64a9192d4 | ||
|
|
8e79e49ae3 | ||
|
|
46ce04355f | ||
|
|
85c68e6876 | ||
|
|
d1dfc4a06a | ||
|
|
02f0bdb8d3 | ||
|
|
6b1225da02 | ||
|
|
4ea31820aa | ||
|
|
8e1630843a | ||
|
|
2d4817841f | ||
|
|
4226b6d0e1 | ||
|
|
3e2b8f89aa |
@@ -13,6 +13,7 @@
|
|||||||
*.orig
|
*.orig
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.DS_Store
|
||||||
index.yaml
|
index.yaml
|
||||||
routes.py
|
routes.py
|
||||||
logging.conf
|
logging.conf
|
||||||
|
|||||||
+13
-2
@@ -1,13 +1,21 @@
|
|||||||
language: python
|
language: python
|
||||||
|
|
||||||
sudo: false
|
sudo: required
|
||||||
|
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
|
dist: "trusty"
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- '2.7'
|
- '2.7'
|
||||||
- 'pypy'
|
|
||||||
- '3.5'
|
- '3.5'
|
||||||
|
- '3.6'
|
||||||
|
- 'pypy-5.3.1'
|
||||||
|
- 'pypy3.5-5.7.1-beta'
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- python: 'pypy3.5-5.7.1-beta'
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install -e .
|
- pip install -e .
|
||||||
@@ -32,3 +40,6 @@ notifications:
|
|||||||
|
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.4"
|
postgresql: "9.4"
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- postgresql-9.4-postgis-2.3
|
||||||
|
|||||||
@@ -1,8 +1,29 @@
|
|||||||
|
## 2.15.0b1
|
||||||
## 2.15.x
|
- dropped support for python 2.6
|
||||||
- web2py does not support python 2.6 anymore
|
- dropped web shell
|
||||||
- py3.5 syntax compatible (see #1353 for details)
|
- experimental python 3 support
|
||||||
- dropped web shell from admin
|
- 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
|
- scheduler new feature: you can now specify intervals with cron
|
||||||
- gluon/* removed from sys.path. Applications relying on statements like e.g.
|
- gluon/* removed from sys.path. Applications relying on statements like e.g.
|
||||||
"from storage import Storage"
|
"from storage import Storage"
|
||||||
@@ -10,8 +31,18 @@
|
|||||||
"from gluon.storage import Storage"
|
"from gluon.storage import Storage"
|
||||||
- tests can only be run with the usual web2py.py --run_system_tests OR with
|
- 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
|
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
|
## 2.14.6
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ update:
|
|||||||
echo "remember that pymysql was tweaked"
|
echo "remember that pymysql was tweaked"
|
||||||
src:
|
src:
|
||||||
### Use semantic versioning
|
### Use semantic versioning
|
||||||
echo 'Version 2.14.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
echo 'Version 2.15.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||||
### rm -f all junk files
|
### rm -f all junk files
|
||||||
make clean
|
make clean
|
||||||
### clean up baisc apps
|
### clean up baisc apps
|
||||||
@@ -54,7 +54,7 @@ src:
|
|||||||
### build web2py_src.zip
|
### build web2py_src.zip
|
||||||
echo '' > NEWINSTALL
|
echo '' > NEWINSTALL
|
||||||
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
||||||
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/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 web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/fabfile.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||||
|
|
||||||
mdp:
|
mdp:
|
||||||
make src
|
make src
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.14.6-stable+timestamp.2016.05.09.19.18.48
|
Version 2.15.2-stable+timestamp.2017.07.19.01.21.31
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ import gluon.contenttype
|
|||||||
import gluon.fileutils
|
import gluon.fileutils
|
||||||
from gluon._compat import iteritems
|
from gluon._compat import iteritems
|
||||||
|
|
||||||
try:
|
|
||||||
import pygraphviz as pgv
|
|
||||||
except ImportError:
|
|
||||||
pgv = None
|
|
||||||
|
|
||||||
is_gae = request.env.web2py_runtime_gae or False
|
is_gae = request.env.web2py_runtime_gae or False
|
||||||
|
|
||||||
# ## critical --- make a copy of the environment
|
# ## critical --- make a copy of the environment
|
||||||
@@ -465,6 +460,7 @@ def ccache():
|
|||||||
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
||||||
|
|
||||||
for key in cache.disk.storage:
|
for key in cache.disk.storage:
|
||||||
|
value = cache.disk.storage[key]
|
||||||
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
|
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
|
||||||
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
||||||
disk['misses'] = value[1]['misses']
|
disk['misses'] = value[1]['misses']
|
||||||
@@ -564,57 +560,6 @@ def table_template(table):
|
|||||||
_cellborder=0, _cellspacing=0)
|
_cellborder=0, _cellspacing=0)
|
||||||
).xml()
|
).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():
|
def manage():
|
||||||
tables = manager_action['tables']
|
tables = manager_action['tables']
|
||||||
if isinstance(tables[0], str):
|
if isinstance(tables[0], str):
|
||||||
@@ -699,3 +644,52 @@ 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_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)
|
ul_main.append(ul_t)
|
||||||
return ul_main
|
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"
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
nodes = []
|
||||||
|
links = []
|
||||||
|
|
||||||
|
subgraphs = dict()
|
||||||
|
|
||||||
|
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
|
import code
|
||||||
from gluon.debug import communicate, web_debugger, dbg_debugger
|
from gluon.debug import communicate, web_debugger, dbg_debugger
|
||||||
from gluon._compat import thread
|
from gluon._compat import thread
|
||||||
|
from gluon.fileutils import open_file
|
||||||
import pydoc
|
import pydoc
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ def interact():
|
|||||||
if filename:
|
if filename:
|
||||||
# prevent IOError 2 on some circuntances (EAFP instead of os.access)
|
# prevent IOError 2 on some circuntances (EAFP instead of os.access)
|
||||||
try:
|
try:
|
||||||
lines = open(filename).readlines()
|
lines = open_file(filename, 'r').readlines()
|
||||||
except:
|
except:
|
||||||
lines = ""
|
lines = ""
|
||||||
lines = dict([(i + 1, l) for (i, l) in enumerate(
|
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 gluon.compileapp import find_exposed_functions
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from gluon._compat import iteritems, PY2, pickle, xrange, urlopen, to_bytes, StringIO, to_native
|
from gluon._compat import iteritems, PY2, pickle, xrange, urlopen, to_bytes, StringIO, to_native
|
||||||
|
import gluon.rewrite
|
||||||
import shutil
|
import shutil
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
@@ -249,6 +250,7 @@ def site():
|
|||||||
db.app.insert(name=appname, owner=auth.user.id)
|
db.app.insert(name=appname, owner=auth.user.id)
|
||||||
log_progress(appname)
|
log_progress(appname)
|
||||||
session.flash = T('new application "%s" created', appname)
|
session.flash = T('new application "%s" created', appname)
|
||||||
|
gluon.rewrite.load()
|
||||||
redirect(URL('design', args=appname))
|
redirect(URL('design', args=appname))
|
||||||
else:
|
else:
|
||||||
session.flash = \
|
session.flash = \
|
||||||
@@ -266,6 +268,7 @@ def site():
|
|||||||
new_repo = git.Repo.clone_from(form_update.vars.url, target)
|
new_repo = git.Repo.clone_from(form_update.vars.url, target)
|
||||||
session.flash = T('new application "%s" imported',
|
session.flash = T('new application "%s" imported',
|
||||||
form_update.vars.name)
|
form_update.vars.name)
|
||||||
|
gluon.rewrite.load()
|
||||||
except git.GitCommandError as err:
|
except git.GitCommandError as err:
|
||||||
session.flash = T('Invalid git repository specified.')
|
session.flash = T('Invalid git repository specified.')
|
||||||
redirect(URL(r=request))
|
redirect(URL(r=request))
|
||||||
@@ -302,6 +305,7 @@ def site():
|
|||||||
log_progress(appname)
|
log_progress(appname)
|
||||||
session.flash = T(msg, dict(appname=appname,
|
session.flash = T(msg, dict(appname=appname,
|
||||||
digest=md5_hash(installed)))
|
digest=md5_hash(installed)))
|
||||||
|
gluon.rewrite.load()
|
||||||
else:
|
else:
|
||||||
msg = 'unable to install application "%(appname)s"'
|
msg = 'unable to install application "%(appname)s"'
|
||||||
session.flash = T(msg, dict(appname=form_update.vars.name))
|
session.flash = T(msg, dict(appname=form_update.vars.name))
|
||||||
@@ -1861,7 +1865,6 @@ def user():
|
|||||||
|
|
||||||
def reload_routes():
|
def reload_routes():
|
||||||
""" Reload routes.py """
|
""" Reload routes.py """
|
||||||
import gluon.rewrite
|
|
||||||
gluon.rewrite.load()
|
gluon.rewrite.load()
|
||||||
redirect(URL('site'))
|
redirect(URL('site'))
|
||||||
|
|
||||||
@@ -1960,7 +1963,7 @@ def git_push():
|
|||||||
|
|
||||||
def plugins():
|
def plugins():
|
||||||
app = request.args(0)
|
app = request.args(0)
|
||||||
from serializers import loads_json
|
from gluon.serializers import loads_json
|
||||||
if not session.plugins:
|
if not session.plugins:
|
||||||
try:
|
try:
|
||||||
rawlist = urlopen("http://www.web2pyslices.com/" +
|
rawlist = urlopen("http://www.web2pyslices.com/" +
|
||||||
|
|||||||
+138
-137
@@ -21,18 +21,19 @@
|
|||||||
'**%(items)s** items, **%(bytes)s** %%{byte(bytes)}': '**%(items)s** items, **%(bytes)s** %%{byte(bytes)}',
|
'**%(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)',
|
'**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',
|
'@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)',
|
'``**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: %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',
|
'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',
|
'Abort': 'Abort',
|
||||||
'About': 'à propos',
|
'About': 'À propos',
|
||||||
'About application': "A propos de l'application",
|
'About application': "À propos de l'application",
|
||||||
'Accept Terms': 'Termes acceptés',
|
'Accept Terms': 'Termes acceptés',
|
||||||
'Add breakpoint': 'Ajouter une interruption',
|
'Add breakpoint': 'Ajouter une interruption',
|
||||||
'additional code for your application': 'code supplémentaire pour votre application',
|
'additional code for your application': 'code supplémentaire pour votre application',
|
||||||
'Additional code for your application': 'Code additionnel 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 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 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',
|
'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 language': "Language de l'admin",
|
||||||
'Admin versioning page': 'Admin versioning page',
|
'Admin versioning page': 'Admin versioning page',
|
||||||
'administrative interface': "interface d'administration",
|
'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',
|
'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 (required):': 'et renommez-la (obligatoire):',
|
||||||
'and rename it:': 'et renommez-le:',
|
'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 is compiled and cannot be designed': "l'application est compilée et ne peut être modifiée",
|
||||||
'Application name:': "Nom de l'application:",
|
'Application name:': "Nom de l'application:",
|
||||||
'Application updated via git pull': 'Application updated via git pull',
|
'Application updated via git pull': 'Application updated via git pull',
|
||||||
'are not used': 'are not used',
|
'are not used': 'ne sont pas utilisé',
|
||||||
'are not used yet': 'are not used yet',
|
'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 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 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 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 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 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',
|
'arguments': 'arguments',
|
||||||
'at char %s': 'at char %s',
|
'at char %s': 'at char %s',
|
||||||
'at line %s': 'at line %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: 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!",
|
'ATTENTION: you cannot edit the running application!': "ATTENTION: vous ne pouvez pas modifier l'application qui tourne!",
|
||||||
'Autocomplete Python Code': 'Autocomplete Python Code',
|
'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',
|
'Available Databases and Tables': 'Available Databases and Tables',
|
||||||
'back': 'retour',
|
'back': 'retour',
|
||||||
'Back to the plugins list': 'Retour à la liste de plugins',
|
'Back to the plugins list': 'Retour à la liste de plugins',
|
||||||
'Back to wizard': 'Back to wizard',
|
'Back to wizard': 'Back to wizard',
|
||||||
'Basics': 'Basics',
|
'Basics': 'Basics',
|
||||||
'Begin': 'Begin',
|
'Begin': 'Début',
|
||||||
'breakpoint': 'breakpoint',
|
'breakpoint': 'breakpoint',
|
||||||
'Breakpoints': 'Breakpoints',
|
'Breakpoints': 'Breakpoints',
|
||||||
'breakpoints': 'breakpoints',
|
'breakpoints': 'breakpoints',
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
'Cache Keys': 'Cache Keys',
|
'Cache Keys': 'Cache Keys',
|
||||||
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyés',
|
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyés',
|
||||||
'can be a git repo': 'can be a git repo',
|
'can be a git repo': 'can be a git repo',
|
||||||
'Cancel': 'Retour',
|
'Cancel': 'Annuler',
|
||||||
'Cannot be empty': 'Ne peut pas être vide',
|
'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. 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:',
|
'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 admin password': 'Changer le mot de passe admin',
|
||||||
'change editor settings': 'change editor settings',
|
'change editor settings': 'change editor settings',
|
||||||
'Changelog': 'Changelog',
|
'Changelog': 'Changelog',
|
||||||
'check all': 'tout vérifier ',
|
'check all': 'tout sélectionner',
|
||||||
'Check for upgrades': 'Vérifier les mises à jour',
|
'Check for upgrades': 'Vérifier les mises à jour',
|
||||||
'Check to delete': 'Cocher pour supprimer',
|
'Check to delete': 'Cocher pour supprimer',
|
||||||
'Checking for upgrades...': 'Vérification des mises à jour ... ',
|
'Checking for upgrades...': 'Vérification des mises à jour ... ',
|
||||||
'Clean': 'nettoyer',
|
'Clean': 'nettoyer',
|
||||||
'Clear': 'Clear',
|
'Clear': 'Effacer',
|
||||||
'Clear CACHE?': 'Clear CACHE?',
|
'Clear CACHE?': 'Effacer le CACHE?',
|
||||||
'Clear DISK': 'Clear DISK',
|
'Clear DISK': 'Effacer le DISQUE',
|
||||||
'Clear RAM': 'Clear RAM',
|
'Clear RAM': 'Effacer la RAM',
|
||||||
'Click row to expand traceback': 'Click row to expand traceback',
|
'Click row to expand traceback': 'Click row to expand traceback',
|
||||||
'Click row to view a ticket': 'Click row to view a ticket',
|
'Click row to view a ticket': 'Click row to view a ticket',
|
||||||
'click to check for upgrades': 'Cliquez pour vérifier les mises jour',
|
'click to check for upgrades': 'Cliquez pour vérifier les mises jour',
|
||||||
'code': 'code',
|
'code': 'code',
|
||||||
'Code listing': 'Code listing',
|
'Code listing': 'Code listing',
|
||||||
'collapse/expand all': 'tout réduire/agrandir',
|
'collapse/expand all': 'tout réduire/agrandir',
|
||||||
'Command': 'Command',
|
'Command': 'Commande',
|
||||||
'Comment:': 'Comment:',
|
'Comment:': 'Commentaire:',
|
||||||
'Commit': 'Commit',
|
'Commit': 'Valider',
|
||||||
'Commit form': 'Commit form',
|
'Commit form': 'Valider le formulaire',
|
||||||
'Committed files': 'Committed files',
|
'Committed files': 'Fichiers validés',
|
||||||
'Compile': 'compiler',
|
'Compile': 'compiler',
|
||||||
'Compile (all or nothing)': 'Compile (all or nothing)',
|
'Compile (all or nothing)': 'Compile (all or nothing)',
|
||||||
'Compile (skip failed views)': 'Compile (skip failed views)',
|
'Compile (skip failed views)': 'Compile (skip failed views)',
|
||||||
'compiled application removed': 'application compilée enlevée',
|
'compiled application removed': 'application compilée enlevée',
|
||||||
'Condition': 'Condition',
|
'Condition': 'Condition',
|
||||||
'continue': 'continue',
|
'continue': 'continuer',
|
||||||
'Controllers': 'Contrôleurs',
|
'Controllers': 'Contrôleurs',
|
||||||
'controllers': 'contrôleurs',
|
'controllers': 'contrôleurs',
|
||||||
'Count': 'Count',
|
'Count': 'Compte',
|
||||||
'Create': 'Créer',
|
'Create': 'Créer',
|
||||||
'create file with filename:': 'créer un fichier avec nom de fichier:',
|
'create file with filename:': 'créer un fichier avec nom de fichier:',
|
||||||
'create new application:': 'créer une nouvelle application:',
|
'create new application:': 'créer une nouvelle application:',
|
||||||
'Create new simple application': 'Créer une nouvelle application',
|
'Create new simple application': 'Créer une nouvelle application',
|
||||||
'Create/Upload': 'Create/Upload',
|
'Create/Upload': 'Create/Upload',
|
||||||
'created by': 'créé par',
|
'created by': 'créé par',
|
||||||
'Created by:': 'Created by:',
|
'Created by:': 'Créé par:',
|
||||||
'Created On': 'Created On',
|
'Created On': 'Créé le',
|
||||||
'Created on:': 'Created on:',
|
'Created on:': 'Créé le:',
|
||||||
'crontab': 'crontab',
|
'crontab': 'crontab',
|
||||||
'Current request': 'Requête actuelle',
|
'Current request': 'Requête actuelle',
|
||||||
'Current response': 'Réponse actuelle',
|
'Current response': 'Réponse actuelle',
|
||||||
'Current session': 'Session en cours',
|
'Current session': 'Session en cours',
|
||||||
'currently running': 'tourne actuellement',
|
'currently running': 'tourne actuellement',
|
||||||
'currently saved or': 'actuellement enregistré ou',
|
'currently saved or': 'actuellement enregistré ou',
|
||||||
'data uploaded': 'données chargées',
|
'data uploaded': 'données téléversées',
|
||||||
'Database': 'Database',
|
'Database': 'Base de données',
|
||||||
'database': 'base de données',
|
'database': 'base de données',
|
||||||
'Database %s select': 'Database %s select',
|
'Database %s select': 'Database %s select',
|
||||||
'database %s select': 'base de données %s sélectionner',
|
'database %s select': 'base de données %s sélectionner',
|
||||||
'Database administration': 'Database administration',
|
'Database administration': 'Administration base de données',
|
||||||
'database administration': 'administration base de données',
|
'database administration': 'administration base de données',
|
||||||
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
||||||
'Date and Time': 'Date et heure',
|
'Date and Time': 'Date et heure',
|
||||||
'db': 'bdd',
|
'db': 'bd',
|
||||||
'Debug': 'Debug',
|
'Debug': 'Debug',
|
||||||
'defines tables': 'définit les tables',
|
'defines tables': 'définit les tables',
|
||||||
'Delete': 'Supprimer',
|
'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 this file (you will be asked to confirm deletion)': 'Supprimer ce fichier (on vous demandera de confirmer la suppression)',
|
||||||
'Delete:': 'Supprimer:',
|
'Delete:': 'Supprimer:',
|
||||||
'deleted after first hit': 'deleted after first hit',
|
'deleted after first hit': 'deleted after first hit',
|
||||||
'Demo': 'Demo',
|
'Demo': 'Démo',
|
||||||
'Deploy': 'Déployer',
|
'Deploy': 'Déployer',
|
||||||
'Deploy on Google App Engine': 'Déployer sur Google App Engine',
|
'Deploy on Google App Engine': 'Déployer sur Google App Engine',
|
||||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||||
@@ -176,22 +177,22 @@
|
|||||||
'Description:': 'Description:',
|
'Description:': 'Description:',
|
||||||
'design': 'conception',
|
'design': 'conception',
|
||||||
'Detailed traceback description': 'Detailed traceback description',
|
'Detailed traceback description': 'Detailed traceback description',
|
||||||
'details': 'details',
|
'details': 'détails',
|
||||||
'direction: ltr': 'direction: ltr',
|
'direction: ltr': 'direction: ltr',
|
||||||
'directory not found': 'directory not found',
|
'directory not found': 'directory not found',
|
||||||
'Disable': 'Disable',
|
'Disable': 'Désactiver',
|
||||||
'Disabled': 'Disabled',
|
'Disabled': 'Désactivé',
|
||||||
'disabled in demo mode': 'disabled in demo mode',
|
'disabled in demo mode': 'disabled in demo mode',
|
||||||
'disabled in GAE mode': 'disabled in GAE mode',
|
'disabled in GAE mode': 'disabled in GAE mode',
|
||||||
'disabled in multi user mode': 'disabled in multi user mode',
|
'disabled in multi user mode': 'disabled in multi user mode',
|
||||||
'DISK': 'DISK',
|
'DISK': 'DISQUE',
|
||||||
'Disk Cache Keys': 'Disk Cache Keys',
|
'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.',
|
'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',
|
'Display line numbers': 'Display line numbers',
|
||||||
'DO NOT use the "Pack compiled" feature.': 'DO NOT use the "Pack compiled" feature.',
|
'DO NOT use the "Pack compiled" feature.': 'DO NOT use the "Pack compiled" feature.',
|
||||||
'docs': 'docs',
|
'docs': 'documents',
|
||||||
'Docs': 'Docs',
|
'Docs': 'Documents',
|
||||||
'done!': 'fait!',
|
'done!': 'fait!',
|
||||||
'Downgrade': 'Downgrade',
|
'Downgrade': 'Downgrade',
|
||||||
'Download .w2p': 'Download .w2p',
|
'Download .w2p': 'Download .w2p',
|
||||||
@@ -201,43 +202,43 @@
|
|||||||
'download plugins': 'télécharger plugins',
|
'download plugins': 'télécharger plugins',
|
||||||
'Download plugins from repository': 'Download plugins from repository',
|
'Download plugins from repository': 'Download plugins from repository',
|
||||||
'EDIT': 'MODIFIER',
|
'EDIT': 'MODIFIER',
|
||||||
'Edit': 'modifier',
|
'Edit': 'Modifier',
|
||||||
'edit all': 'edit all',
|
'edit all': 'tout modifier',
|
||||||
'Edit application': "Modifier l'application",
|
'Edit application': "Modifier l'application",
|
||||||
'edit controller': 'modifier contrôleur',
|
'edit controller': 'modifier contrôleur',
|
||||||
'edit controller:': 'edit controller:',
|
'edit controller:': 'modifier le contrôleur:',
|
||||||
'Edit current record': 'Modifier cette entrée',
|
'Edit current record': 'Modifier cet enregistrement',
|
||||||
'edit views:': 'modifier vues:',
|
'edit views:': 'modifier les vues:',
|
||||||
'Editing %s': 'Editing %s',
|
'Editing %s': 'Modifier %s',
|
||||||
'Editing file': 'Modifier le fichier',
|
'Editing file': 'Modifier le fichier',
|
||||||
'Editing file "%s"': 'Modifier le fichier "% s" ',
|
'Editing file "%s"': 'Modifier le fichier "% s" ',
|
||||||
'Editing Language file': 'Modifier le fichier de langue',
|
'Editing Language file': 'Modifier le fichier de langue',
|
||||||
'Editing Plural Forms File': 'Editing Plural Forms File',
|
'Editing Plural Forms File': 'Modifier le fichier du formulaire pluriel',
|
||||||
'Editor': 'Editor',
|
'Editor': 'Éditeur',
|
||||||
'Email Address': 'Email Address',
|
'Email Address': 'Adresse courriel',
|
||||||
'Enable': 'Enable',
|
'Enable': 'Activer',
|
||||||
'Enable Close-Tag': 'Enable Close-Tag',
|
'Enable Close-Tag': 'Enable Close-Tag',
|
||||||
'Enable Code Folding': 'Enable Code Folding',
|
'Enable Code Folding': 'Enable Code Folding',
|
||||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||||
'Error': 'Error',
|
'Error': 'Error',
|
||||||
'Error logs for "%(app)s"': 'Journal d\'erreurs pour "%(app)s"',
|
'Error logs for "%(app)s"': 'Journal d\'erreurs pour "%(app)s"',
|
||||||
'Error snapshot': 'Error snapshot',
|
'Error snapshot': 'Error snapshot',
|
||||||
'Error ticket': 'Error ticket',
|
'Error ticket': "Billet d'erreur",
|
||||||
'Errors': 'erreurs',
|
'Errors': 'Erreurs',
|
||||||
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
|
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
|
||||||
'Exception %s': 'Exception %s',
|
'Exception %s': 'Exception %s',
|
||||||
'Exception instance attributes': "Attributs d'instance Exception",
|
'Exception instance attributes': "Attributs d'instance Exception",
|
||||||
'Exit Fullscreen': 'Exit Fullscreen',
|
'Exit Fullscreen': 'Exit Fullscreen',
|
||||||
'Expand Abbreviation (html files only)': 'Expand Abbreviation (html files only)',
|
'Expand Abbreviation (html files only)': 'Expand Abbreviation (html files only)',
|
||||||
'export as csv file': 'export au format CSV',
|
'export as csv file': 'export au format CSV',
|
||||||
'Exports:': 'Exports:',
|
'Exports:': 'Exportions:',
|
||||||
'exposes': 'expose',
|
'exposes': 'expose',
|
||||||
'exposes:': 'expose:',
|
'exposes:': 'expose:',
|
||||||
'extends': 'étend',
|
'extends': 'étend',
|
||||||
'failed to compile file because:': 'failed to compile file because:',
|
'failed to compile file because:': 'failed to compile file because:',
|
||||||
'failed to reload module': 'impossible de recharger le module',
|
'failed to reload module': 'impossible de recharger le module',
|
||||||
'failed to reload module because:': 'impossible de recharger le module car:',
|
'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" created': 'fichier "%(filename)s" créé',
|
||||||
'file "%(filename)s" deleted': 'fichier "%(filename)s" supprimé',
|
'file "%(filename)s" deleted': 'fichier "%(filename)s" supprimé',
|
||||||
'file "%(filename)s" uploaded': 'fichier "%(filename)s" chargé',
|
'file "%(filename)s" uploaded': 'fichier "%(filename)s" chargé',
|
||||||
@@ -247,19 +248,19 @@
|
|||||||
'file not found': 'file not found',
|
'file not found': 'file not found',
|
||||||
'file saved on %(time)s': 'fichier enregistré le %(time)s',
|
'file saved on %(time)s': 'fichier enregistré le %(time)s',
|
||||||
'file saved on %s': 'fichier enregistré le %s',
|
'file saved on %s': 'fichier enregistré le %s',
|
||||||
'filename': 'filename',
|
'filename': 'nom de fichier',
|
||||||
'Filename': 'Filename',
|
'Filename': 'Nom de fichier',
|
||||||
'Files added': 'Files added',
|
'Files added': 'Fichiers ajoutés',
|
||||||
'filter': 'filtre',
|
'filter': 'filtre',
|
||||||
'Find Next': 'Find Next',
|
'Find Next': 'Trouver les suivants',
|
||||||
'Find Previous': 'Find Previous',
|
'Find Previous': 'Trouver les précédents',
|
||||||
'Form has errors': 'Form has errors',
|
'Form has errors': 'Le formulaire comporte des erreurs',
|
||||||
'Frames': 'Frames',
|
'Frames': 'Frames',
|
||||||
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraîneront des tests [passed] .',
|
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraîneront des tests [passed] .',
|
||||||
'GAE Email': 'GAE Email',
|
'GAE Email': 'GAE Email',
|
||||||
'GAE Output': 'GAE Output',
|
'GAE Output': 'GAE Output',
|
||||||
'GAE Password': 'GAE Password',
|
'GAE Password': 'Mot de passe GAE',
|
||||||
'Generate': 'Generate',
|
'Generate': 'Générer',
|
||||||
'Git Pull': 'Git Pull',
|
'Git Pull': 'Git Pull',
|
||||||
'Git Push': 'Git Push',
|
'Git Push': 'Git Push',
|
||||||
'Globals##debug': 'Globals##debug',
|
'Globals##debug': 'Globals##debug',
|
||||||
@@ -267,15 +268,15 @@
|
|||||||
'Google App Engine Deployment Interface': 'Google App Engine Deployment Interface',
|
'Google App Engine Deployment Interface': 'Google App Engine Deployment Interface',
|
||||||
'Google Application Id': 'Google Application Id',
|
'Google Application Id': 'Google Application Id',
|
||||||
'Goto': 'Goto',
|
'Goto': 'Goto',
|
||||||
'graph model': 'graph model',
|
'graph model': 'représentation graphique du modèle',
|
||||||
'Graph Model': 'Graph Model',
|
'Graph Model': 'Représentation graphique du modèle',
|
||||||
'Help': 'aide',
|
'Help': 'aide',
|
||||||
'here': 'here',
|
'here': 'ici',
|
||||||
'Hide/Show Translated strings': 'Hide/Show Translated strings',
|
'Hide/Show Translated strings': 'Hide/Show Translated strings',
|
||||||
'Highlight current line': 'Highlight current line',
|
'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)})',
|
'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',
|
'Hits': 'Hits',
|
||||||
'Home': 'Home',
|
'Home': 'Accueil',
|
||||||
'honored only if the expression evaluates to true': 'honored only if the expression evaluates to true',
|
'honored only if the expression evaluates to true': 'honored only if the expression evaluates to true',
|
||||||
'htmledit': 'edition html',
|
'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',
|
'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.\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 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.',
|
'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',
|
'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.',
|
'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',
|
'includes': 'inclus',
|
||||||
@@ -311,14 +312,14 @@
|
|||||||
'Invalid request': 'Invalid request',
|
'Invalid request': 'Invalid request',
|
||||||
'invalid table names (auth_* tables already defined)': 'invalid table names (auth_* tables already defined)',
|
'invalid table names (auth_* tables already defined)': 'invalid table names (auth_* tables already defined)',
|
||||||
'invalid ticket': 'ticket non valide',
|
'invalid ticket': 'ticket non valide',
|
||||||
'Key': 'Key',
|
'Key': 'Clé',
|
||||||
'Keyboard shortcuts': 'Keyboard shortcuts',
|
'Keyboard shortcuts': 'Keyboard shortcuts',
|
||||||
'kill process': 'kill process',
|
'kill process': 'kill process',
|
||||||
'language file "%(filename)s" created/updated': 'fichier de langue "%(filename)s" créé/mis à jour',
|
'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 ',
|
'Language files (static strings) updated': 'Fichiers de langue (chaînes statiques) mis à jour ',
|
||||||
'languages': 'langues',
|
'languages': 'langues',
|
||||||
'Languages': 'Langues',
|
'Languages': 'Langues',
|
||||||
'Last Revision': 'Last Revision',
|
'Last Revision': 'Dernière révision',
|
||||||
'Last saved on:': 'Dernière sauvegarde le:',
|
'Last saved on:': 'Dernière sauvegarde le:',
|
||||||
'License for': 'Licence pour',
|
'License for': 'Licence pour',
|
||||||
'License:': 'License:',
|
'License:': 'License:',
|
||||||
@@ -326,8 +327,8 @@
|
|||||||
'Line number': 'Line number',
|
'Line number': 'Line number',
|
||||||
'lists by exception': 'lists by exception',
|
'lists by exception': 'lists by exception',
|
||||||
'lists by ticket': 'lists by ticket',
|
'lists by ticket': 'lists by ticket',
|
||||||
'Loading...': 'Loading...',
|
'Loading...': 'Chargement...',
|
||||||
'loading...': 'Chargement ...',
|
'loading...': 'chargement ...',
|
||||||
'Local Apps': 'Local Apps',
|
'Local Apps': 'Local Apps',
|
||||||
'locals': 'locals',
|
'locals': 'locals',
|
||||||
'Locals##debug': 'Locals##debug',
|
'Locals##debug': 'Locals##debug',
|
||||||
@@ -337,7 +338,7 @@
|
|||||||
'Login to the Administrative Interface': "Se connecter à l'interface d'administration",
|
'Login to the Administrative Interface': "Se connecter à l'interface d'administration",
|
||||||
'Login/Register': 'Login/Register',
|
'Login/Register': 'Login/Register',
|
||||||
'Logout': 'déconnexion',
|
'Logout': 'déconnexion',
|
||||||
'lost password': 'lost password',
|
'lost password': 'mot de passe perdu',
|
||||||
'Main Menu': 'Main Menu',
|
'Main Menu': 'Main Menu',
|
||||||
'Manage': 'Manage',
|
'Manage': 'Manage',
|
||||||
'Manage %(action)s': 'Manage %(action)s',
|
'Manage %(action)s': 'Manage %(action)s',
|
||||||
@@ -350,7 +351,7 @@
|
|||||||
'merge': 'fusionner',
|
'merge': 'fusionner',
|
||||||
'Models': 'Modèles',
|
'Models': 'Modèles',
|
||||||
'models': 'modèles',
|
'models': 'modèles',
|
||||||
'Modified On': 'Modified On',
|
'Modified On': 'Modifié le',
|
||||||
'Modules': 'Modules',
|
'Modules': 'Modules',
|
||||||
'modules': 'modules',
|
'modules': 'modules',
|
||||||
'Multi User Mode': 'Multi User Mode',
|
'Multi User Mode': 'Multi User Mode',
|
||||||
@@ -363,8 +364,8 @@
|
|||||||
'New Record': 'Nouvelle Entrée',
|
'New Record': 'Nouvelle Entrée',
|
||||||
'new record inserted': 'nouvelle entrée insérée',
|
'new record inserted': 'nouvelle entrée insérée',
|
||||||
'New simple application': 'Nouvelle application simple',
|
'New simple application': 'Nouvelle application simple',
|
||||||
'next': 'next',
|
'next': 'suivant',
|
||||||
'next %s rows': 'next %s rows',
|
'next %s rows': '%s lignes suivantes',
|
||||||
'next 100 rows': '100 lignes suivantes',
|
'next 100 rows': '100 lignes suivantes',
|
||||||
'NO': 'NON',
|
'NO': 'NON',
|
||||||
'no changes': 'no changes',
|
'no changes': 'no changes',
|
||||||
@@ -374,8 +375,8 @@
|
|||||||
'no package selected': 'no package selected',
|
'no package selected': 'no package selected',
|
||||||
'no permission to uninstall "%s"': 'no permission to uninstall "%s"',
|
'no permission to uninstall "%s"': 'no permission to uninstall "%s"',
|
||||||
'Node:': 'Node:',
|
'Node:': 'Node:',
|
||||||
'Not Authorized': 'Not Authorized',
|
'Not Authorized': 'Pas autorisé',
|
||||||
'Not supported': 'Not supported',
|
'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.',
|
'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**',
|
'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.",
|
"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',
|
'Peeking at file': 'Jeter un oeil au fichier',
|
||||||
'Permission': 'Permission',
|
'Permission': 'Permission',
|
||||||
'Permissions': 'Permissions',
|
'Permissions': 'Permissions',
|
||||||
'Please': 'Please',
|
'Please': 'SVP',
|
||||||
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
|
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
|
||||||
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" supprimé',
|
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" supprimé',
|
||||||
'Plugin "%s" in application': 'Plugin "%s" dans l\'application',
|
'Plugin "%s" in application': 'Plugin "%s" dans l\'application',
|
||||||
'plugin not specified': 'plugin not specified',
|
'plugin not specified': 'plugin not specified',
|
||||||
'Plugin page': 'Plugin page',
|
'Plugin page': 'Plugin page',
|
||||||
'plugins': 'plugins',
|
'plugins': 'plugiciels',
|
||||||
'Plugins': 'Plugins',
|
'Plugins': 'Plugiciels',
|
||||||
'Plural Form #%s': 'Plural Form #%s',
|
'Plural Form #%s': 'Plural Form #%s',
|
||||||
'Plural-Forms:': 'Plural-Forms:',
|
'Plural-Forms:': 'Plural-Forms:',
|
||||||
'Powered by': 'Propulsé par',
|
'Powered by': 'Propulsé par',
|
||||||
@@ -423,7 +424,7 @@
|
|||||||
'Pull': 'Pull',
|
'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 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.',
|
'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.',
|
'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',
|
'pygraphviz library not found': 'pygraphviz library not found',
|
||||||
'PythonAnywhere Apps': 'PythonAnywhere Apps',
|
'PythonAnywhere Apps': 'PythonAnywhere Apps',
|
||||||
@@ -433,9 +434,9 @@
|
|||||||
'RAM Cache Keys': 'RAM Cache Keys',
|
'RAM Cache Keys': 'RAM Cache Keys',
|
||||||
'Ram Cleared': 'Ram Cleared',
|
'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.',
|
'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',
|
'Rapid Search': 'Recherche rapide',
|
||||||
'Record': 'Record',
|
'Record': 'Enregistrement',
|
||||||
'record': 'entrée',
|
'record': 'enregistrement',
|
||||||
'record does not exist': "l'entrée n'existe pas",
|
'record does not exist': "l'entrée n'existe pas",
|
||||||
'record id': 'id entrée',
|
'record id': 'id entrée',
|
||||||
'Record id': 'Record id',
|
'Record id': 'Record id',
|
||||||
@@ -444,24 +445,24 @@
|
|||||||
'Reload routes': 'Reload routes',
|
'Reload routes': 'Reload routes',
|
||||||
'Remove compiled': 'retirer compilé',
|
'Remove compiled': 'retirer compilé',
|
||||||
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
|
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
|
||||||
'Replace': 'Replace',
|
'Replace': 'Remplacer',
|
||||||
'Replace All': 'Replace All',
|
'Replace All': 'Tout remplacer',
|
||||||
'Repository (%s)': 'Repository (%s)',
|
'Repository (%s)': 'Repository (%s)',
|
||||||
'request': 'request',
|
'request': 'request',
|
||||||
'requires distutils, but not installed': 'requires distutils, but not installed',
|
'requires distutils, but not installed': 'requires distutils, but not installed',
|
||||||
'requires python-git, but not installed': 'requires python-git, but not installed',
|
'requires python-git, but not installed': 'requires python-git, but not installed',
|
||||||
'Resolve Conflict file': 'Résoudre les conflits de fichiers',
|
'Resolve Conflict file': 'Résoudre les conflits de fichiers',
|
||||||
'response': 'response',
|
'response': 'response',
|
||||||
'restart': 'restart',
|
'restart': 'redémarrer',
|
||||||
'restore': 'restaurer',
|
'restore': 'restaurer',
|
||||||
'return': 'return',
|
'return': 'retour',
|
||||||
'Revert': 'Revert',
|
'Revert': "Revenir vers l'arrière",
|
||||||
'revert': 'revenir',
|
'revert': "revenir vers l'arrière",
|
||||||
'reverted to revision %s': 'reverted to revision %s',
|
'reverted to revision %s': 'reverted to revision %s',
|
||||||
'Revision %s': 'Revision %s',
|
'Revision %s': 'Révision %s',
|
||||||
'Revision:': 'Revision:',
|
'Revision:': 'Révision:',
|
||||||
'Role': 'Role',
|
'Role': 'Rôle',
|
||||||
'Roles': 'Roles',
|
'Roles': 'Rôles',
|
||||||
'Rows in Table': 'Rows in Table',
|
'Rows in Table': 'Rows in Table',
|
||||||
'Rows in table': 'Lignes de la table',
|
'Rows in table': 'Lignes de la table',
|
||||||
'Rows selected': 'Lignes sélectionnées',
|
'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': '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')",
|
"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',
|
'Running on %s': 'Running on %s',
|
||||||
'Save': 'Enregistrer',
|
'Save': 'Sauvegarder',
|
||||||
'save': 'sauver',
|
'save': 'sauvegarder',
|
||||||
'Save file:': 'Save file:',
|
'Save file:': 'Sauvegarder le fichier:',
|
||||||
'Save file: %s': 'Save file: %s',
|
'Save file: %s': 'Sauvegarder le fichier : %s',
|
||||||
'Save model as...': 'Save model as...',
|
'Save model as...': 'Sauvegarder le modèle sous...',
|
||||||
'Save via Ajax': 'Save via Ajax',
|
'Save via Ajax': 'Sauvegarder via Ajax',
|
||||||
'Saved file hash:': 'Hash du Fichier enregistré:',
|
'Saved file hash:': 'Hash du Fichier enregistré:',
|
||||||
'Screenshot %s': 'Screenshot %s',
|
'Screenshot %s': "Capture d'écran %s",
|
||||||
'Search': 'Search',
|
'Search': 'Rechercher',
|
||||||
'Searching: **%s** %%{file}': 'Searching: **%s** %%{file}',
|
'Searching: **%s** %%{file}': 'Searching: **%s** %%{file}',
|
||||||
'Select Files to Package': 'Select Files to Package',
|
'Select Files to Package': 'Select Files to Package',
|
||||||
'selected': 'sélectionnés',
|
'selected': 'sélectionnés',
|
||||||
@@ -491,34 +492,34 @@
|
|||||||
'Showing %s to %s of %s %s found': 'Showing %s to %s of %s %s found',
|
'Showing %s to %s of %s %s found': 'Showing %s to %s of %s %s found',
|
||||||
'Singular Form': 'Singular Form',
|
'Singular Form': 'Singular Form',
|
||||||
'Site': 'Site',
|
'Site': 'Site',
|
||||||
'Size of cache:': 'Size of cache:',
|
'Size of cache:': 'Taille de la mémoire cache:',
|
||||||
'skip to generate': 'skip to generate',
|
'skip to generate': 'skip to generate',
|
||||||
'some files could not be removed': 'certains fichiers ne peuvent pas être supprimés',
|
'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',
|
'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',
|
'Sorry, could not find mercurial installed': 'Sorry, could not find mercurial installed',
|
||||||
'source : db': 'source : db',
|
'source : db': 'source : db',
|
||||||
'source : filesystem': 'source : filesystem',
|
'source : filesystem': 'source : système de fichier',
|
||||||
'Start a new app': 'Start a new app',
|
'Start a new app': 'Commencer une nouvelle application',
|
||||||
'Start searching': 'Start searching',
|
'Start searching': 'Débuté la recherche',
|
||||||
'Start wizard': "Démarrer l'assistant",
|
'Start wizard': "Démarrer l'assistant",
|
||||||
'state': 'état',
|
'state': 'état',
|
||||||
'Static': 'Static',
|
'Static': 'Statique',
|
||||||
'static': 'statiques',
|
'static': 'statique',
|
||||||
'Static files': 'Fichiers statiques',
|
'Static files': 'Fichiers statiques',
|
||||||
'Statistics': 'Statistics',
|
'Statistics': 'Statistics',
|
||||||
'Step': 'Step',
|
'Step': 'Step',
|
||||||
'step': 'step',
|
'step': 'step',
|
||||||
'stop': 'stop',
|
'stop': 'arrêt',
|
||||||
'submit': 'envoyer',
|
'submit': 'envoyer',
|
||||||
'Submit': 'Submit',
|
'Submit': 'Envoyer',
|
||||||
'successful': 'successful',
|
'successful': 'réussi',
|
||||||
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet? ',
|
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet?',
|
||||||
'switch to : db': 'switch to : db',
|
'switch to : db': 'transférer dans : db',
|
||||||
'switch to : filesystem': 'switch to : filesystem',
|
'switch to : filesystem': 'switch to : filesystem',
|
||||||
'Tab width (# characters)': 'Tab width (# characters)',
|
'Tab width (# characters)': 'Tab width (# characters)',
|
||||||
'table': 'table',
|
'table': 'table',
|
||||||
'Table': 'Table',
|
'Table': 'Table',
|
||||||
'Temporary': 'Temporary',
|
'Temporary': 'Temporaire',
|
||||||
'test': 'tester',
|
'test': 'tester',
|
||||||
'Testing application': "Test de l'application",
|
'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.',
|
'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 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': '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',
|
'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 controllers': "Il n'y a pas de contrôleurs",
|
||||||
'There are no models': "Il n'y a pas de modèles",
|
'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 modules': "Il n'y a pas de modules",
|
||||||
'There are no plugins': "Il n'y a pas de plugins",
|
'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 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 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",
|
'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',
|
'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 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 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".',
|
'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': 'Billet',
|
||||||
'Ticket ID': 'Ticket ID',
|
'Ticket ID': 'Identifiant du Billet',
|
||||||
'Ticket Missing': 'Ticket Missing',
|
'Ticket Missing': 'Billet manquant',
|
||||||
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
|
||||||
'TM': 'MD',
|
'TM': 'MD',
|
||||||
'to previous version.': 'à la version précédente.',
|
'to previous version.': 'à la version précédente.',
|
||||||
@@ -569,8 +570,8 @@
|
|||||||
'Translation strings for the application': "Chaînes de traduction pour l'application",
|
'Translation strings for the application': "Chaînes de traduction pour l'application",
|
||||||
'try': 'essayer',
|
'try': 'essayer',
|
||||||
'try something like': 'essayez quelque chose comme',
|
'try something like': 'essayez quelque chose comme',
|
||||||
'Try the mobile interface': 'Try the mobile interface',
|
'Try the mobile interface': "Essayer l'interface pour mobile",
|
||||||
'try view': 'try view',
|
'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 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.',
|
'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',
|
'Unable to check for upgrades': 'Impossible de vérifier les mises à jour',
|
||||||
@@ -595,25 +596,25 @@
|
|||||||
'update': 'mettre à jour',
|
'update': 'mettre à jour',
|
||||||
'update all languages': 'mettre à jour toutes les langues',
|
'update all languages': 'mettre à jour toutes les langues',
|
||||||
'Update:': 'Mise à jour:',
|
'Update:': 'Mise à jour:',
|
||||||
'Upgrade': 'Upgrade',
|
'Upgrade': 'Mese à jour de version',
|
||||||
'upgrade now': 'mettre à jour maintenant',
|
'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',
|
'upgrade web2py now': 'mettre à jour web2py maintenant',
|
||||||
'upload': 'charger',
|
'upload': 'téléversé',
|
||||||
'Upload': 'Upload',
|
'Upload': 'Téléversé',
|
||||||
'Upload & install packed application': "Charger & installer l'application empaquetée",
|
'Upload & install packed application': "Charger & installer l'application empaquetée",
|
||||||
'Upload a package:': 'Charger un paquet:',
|
'Upload a package:': 'Téléverser un paquet:',
|
||||||
'Upload and install packed application': 'Upload and install packed application',
|
'Upload and install packed application': 'Téléversement et installation du paquet applicatif',
|
||||||
'upload application:': "charger l'application:",
|
'upload application:': "téléverser l'application:",
|
||||||
'Upload existing application': 'Charger une application existante',
|
'Upload existing application': 'Téléverser une application existante',
|
||||||
'upload file:': 'charger le fichier:',
|
'upload file:': 'téléverser le fichier:',
|
||||||
'upload plugin file:': 'charger fichier plugin:',
|
'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 (...)&(...) 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:',
|
'Use an url:': 'Utiliser une url:',
|
||||||
'user': 'utilisateur',
|
'user': 'utilisateur',
|
||||||
'User': 'User',
|
'User': 'Utilisateur',
|
||||||
'Username': 'Username',
|
'Username': "Nom d'utilisateur",
|
||||||
'Users': 'Users',
|
'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.',
|
'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',
|
'variables': 'variables',
|
||||||
'Version': 'Version',
|
'Version': 'Version',
|
||||||
@@ -625,9 +626,9 @@
|
|||||||
'Warning!': 'Warning!',
|
'Warning!': 'Warning!',
|
||||||
'WARNING:': 'WARNING:',
|
'WARNING:': 'WARNING:',
|
||||||
'WARNING: The following views could not be compiled:': 'WARNING: The following views could not be compiled:',
|
'WARNING: The following views could not be compiled:': 'WARNING: The following views could not be compiled:',
|
||||||
'Web Framework': 'Framework Web',
|
'Web Framework': 'Cadre Web',
|
||||||
'web2py Admin Password': 'web2py Admin Password',
|
'web2py Admin Password': 'Mot de passe admin web2py',
|
||||||
'web2py apps to deploy': 'web2py apps to deploy',
|
'web2py apps to deploy': 'applications web2py à deployer',
|
||||||
'web2py Debugger': 'web2py Debugger',
|
'web2py Debugger': 'web2py Debugger',
|
||||||
'web2py downgrade': 'web2py downgrade',
|
'web2py downgrade': 'web2py downgrade',
|
||||||
'web2py is up to date': 'web2py est à jour',
|
'web2py is up to date': 'web2py est à jour',
|
||||||
@@ -635,10 +636,10 @@
|
|||||||
'web2py Recent Tweets': 'Tweets récents sur web2py ',
|
'web2py Recent Tweets': 'Tweets récents sur web2py ',
|
||||||
'web2py upgrade': 'web2py upgrade',
|
'web2py upgrade': 'web2py upgrade',
|
||||||
'web2py upgraded; please restart it': 'web2py mis à jour; veuillez le redémarrer',
|
'web2py upgraded; please restart it': 'web2py mis à jour; veuillez le redémarrer',
|
||||||
'Working...': 'Working...',
|
'Working...': 'Travail en cours...',
|
||||||
'WSGI reference name': 'WSGI reference name',
|
'WSGI reference name': 'WSGI reference name',
|
||||||
'YES': 'OUI',
|
'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 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 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',
|
'You have one more login attempt before you are locked out': 'You have one more login attempt before you are locked out',
|
||||||
|
|||||||
@@ -352,9 +352,9 @@
|
|||||||
'modules': 'modules',
|
'modules': 'modules',
|
||||||
'Multi User Mode': 'Multi User Mode',
|
'Multi User Mode': 'Multi User Mode',
|
||||||
'Must include at least %s %s': 'Moet ten minste bevatten %s %s',
|
'Must include at least %s %s': 'Moet ten minste bevatten %s %s',
|
||||||
'Must include at least %s lower case': 'Moet ten minste bevatten %s lower case',
|
'Must include at least %s lowercase': 'Moet ten minste bevatten %s kleine letter',
|
||||||
'Must include at least %s of the following : %s': 'Moet ten minste bevatten %s van het volgende : %s',
|
'Must include at least %s of the following : %s': 'Moet ten minste bevatten %s van het volgende : %s',
|
||||||
'Must include at least %s upper case': 'Moet ten minste bevatten %s upper case',
|
'Must include at least %s uppercase': 'Moet ten minste bevatten %s hoofdletter',
|
||||||
'new application "%s" created': 'nieuwe applicatie "%s" gemaakt',
|
'new application "%s" created': 'nieuwe applicatie "%s" gemaakt',
|
||||||
'new application "%s" imported': 'new application "%s" imported',
|
'new application "%s" imported': 'new application "%s" imported',
|
||||||
'New Application Wizard': 'Nieuwe Applicatie Wizard',
|
'New Application Wizard': 'Nieuwe Applicatie Wizard',
|
||||||
|
|||||||
@@ -356,9 +356,9 @@
|
|||||||
'modules': 'модулі',
|
'modules': 'модулі',
|
||||||
'Multi User Mode': 'Multi User Mode',
|
'Multi User Mode': 'Multi User Mode',
|
||||||
'Must include at least %s %s': 'Має вміщувати щонайменше %s %s',
|
'Must include at least %s %s': 'Має вміщувати щонайменше %s %s',
|
||||||
'Must include at least %s lower case': 'Повинен включати щонайменше %s малих букв',
|
'Must include at least %s lowercase': 'Повинен включати щонайменше %s малих букв',
|
||||||
'Must include at least %s of the following : %s': 'Має включати не менше %s таких символів : %s',
|
'Must include at least %s of the following : %s': 'Має включати не менше %s таких символів : %s',
|
||||||
'Must include at least %s upper case': 'Повинен включати щонайменше %s великих букв',
|
'Must include at least %s uppercase': 'Повинен включати щонайменше %s великих букв',
|
||||||
'new application "%s" created': 'новий додаток "%s" створено',
|
'new application "%s" created': 'новий додаток "%s" створено',
|
||||||
'new application "%s" imported': 'new application "%s" imported',
|
'new application "%s" imported': 'new application "%s" imported',
|
||||||
'New Application Wizard': 'Майстер створення нового додатку',
|
'New Application Wizard': 'Майстер створення нового додатку',
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def verify_password(password):
|
|||||||
if DEMO_MODE:
|
if DEMO_MODE:
|
||||||
ret = True
|
ret = True
|
||||||
elif not _config.get('password'):
|
elif not _config.get('password'):
|
||||||
ret - False
|
ret = False
|
||||||
elif _config['password'].startswith('pam_user:'):
|
elif _config['password'].startswith('pam_user:'):
|
||||||
session.pam_user = _config['password'][9:].strip()
|
session.pam_user = _config['password'][9:].strip()
|
||||||
import gluon.contrib.pam
|
import gluon.contrib.pam
|
||||||
|
|||||||
+3
-3
@@ -314,8 +314,8 @@
|
|||||||
// Vim does not support modifier only keys.
|
// Vim does not support modifier only keys.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// TODO: Current bindings expect the character to be lower case, but
|
// TODO: Current bindings expect the character to be lowercase, but
|
||||||
// it looks like vim key notation uses upper case.
|
// it looks like vim key notation uses uppercase.
|
||||||
if (isUpperCase(lastPiece)) {
|
if (isUpperCase(lastPiece)) {
|
||||||
pieces[pieces.length - 1] = lastPiece.toLowerCase();
|
pieces[pieces.length - 1] = lastPiece.toLowerCase();
|
||||||
}
|
}
|
||||||
@@ -3644,7 +3644,7 @@
|
|||||||
* Extract the regular expression from the query and return a Regexp object.
|
* Extract the regular expression from the query and return a Regexp object.
|
||||||
* Returns null if the query is blank.
|
* Returns null if the query is blank.
|
||||||
* If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
|
* If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
|
||||||
* If smartCase is passed in, and the query contains upper case letters,
|
* If smartCase is passed in, and the query contains uppercase letters,
|
||||||
* then ignoreCase is overridden, and the 'i' flag will not be set.
|
* then ignoreCase is overridden, and the 'i' flag will not be set.
|
||||||
* If the query contains the /i in the flag part of the regular expression,
|
* If the query contains the /i in the flag part of the regular expression,
|
||||||
* then both ignoreCase and smartCase are ignored, and 'i' will be passed
|
* then both ignoreCase and smartCase are ignored, and 'i' will be passed
|
||||||
|
|||||||
+760
-202
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
|||||||
|
.node {fill: steelblue;
|
||||||
|
stroke: #636363;
|
||||||
|
stroke-width: 1px;}
|
||||||
|
|
||||||
|
.auth {fill: lightgrey;}
|
||||||
|
|
||||||
|
.table {r: 10;}
|
||||||
|
|
||||||
|
.link {stroke: #bbbbbb;
|
||||||
|
stroke-width: 2px;}
|
||||||
|
td {padding: 4px;}
|
||||||
|
|
||||||
|
div.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
text-align: left;
|
||||||
|
/* width: 140px; */
|
||||||
|
/* height: 28px;*/
|
||||||
|
padding: 0px 5px 0px 5px;
|
||||||
|
padding-top: 0px;
|
||||||
|
font: 12px sans-serif;
|
||||||
|
background: #fff7bc;
|
||||||
|
border: solid 1px #aaa;
|
||||||
|
border-radius: 6px;
|
||||||
|
pointer-events: none;}
|
||||||
|
|
||||||
|
h5 { font: 14px sans-serif;
|
||||||
|
background : #ec7014;
|
||||||
|
color: #ffffe5;
|
||||||
|
padding: 5px 2px 5px 2px;
|
||||||
|
margin-top: 1px;}
|
||||||
|
path {
|
||||||
|
fill: #aaaaaa;}
|
||||||
|
|
||||||
+8
File diff suppressed because one or more lines are too long
+181
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
+4
-5
File diff suppressed because one or more lines are too long
@@ -38,8 +38,12 @@
|
|||||||
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
||||||
else $('#' + id).show().fadeOut('slow');
|
else $('#' + id).show().fadeOut('slow');
|
||||||
},
|
},
|
||||||
ajax: function (u, s, t) {
|
ajax: function (u, s, t, options) {
|
||||||
/*simple ajax function*/
|
/*simple ajax function*/
|
||||||
|
|
||||||
|
// set options default value
|
||||||
|
options = typeof options !== 'undefined' ? options : {};
|
||||||
|
|
||||||
var query = '';
|
var query = '';
|
||||||
if (typeof s == 'string') {
|
if (typeof s == 'string') {
|
||||||
var d = $(s).serialize();
|
var d = $(s).serialize();
|
||||||
@@ -59,18 +63,44 @@
|
|||||||
query = pcs.join('&');
|
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',
|
type: 'POST',
|
||||||
url: u,
|
url: u,
|
||||||
data: query,
|
data: query,
|
||||||
success: function (msg) {
|
success: success
|
||||||
if (t) {
|
};
|
||||||
if (t == ':eval') eval(msg);
|
|
||||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
//remove custom "done" option if exists
|
||||||
else t(msg);
|
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) {
|
ajax_fields: function (target) {
|
||||||
/*
|
/*
|
||||||
@@ -168,7 +198,8 @@
|
|||||||
* and require no dom manipulations
|
* and require no dom manipulations
|
||||||
*/
|
*/
|
||||||
var doc = $(document);
|
var doc = $(document);
|
||||||
doc.on('click', '.w2p_flash', function () {
|
doc.on('click', '.w2p_flash', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
var t = $(this);
|
var t = $(this);
|
||||||
if (t.css('top') == '0px') t.slideUp('slow');
|
if (t.css('top') == '0px') t.slideUp('slow');
|
||||||
else t.fadeOut();
|
else t.fadeOut();
|
||||||
@@ -316,6 +347,7 @@
|
|||||||
'beforeSend': function (xhr, settings) {
|
'beforeSend': function (xhr, settings) {
|
||||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||||
xhr.setRequestHeader('web2py-component-element', target);
|
xhr.setRequestHeader('web2py-component-element', target);
|
||||||
|
web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
|
||||||
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
|
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
|
||||||
},
|
},
|
||||||
'success': function (data, status, xhr) {
|
'success': function (data, status, xhr) {
|
||||||
|
|||||||
@@ -233,30 +233,22 @@
|
|||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{if request.function=='graph_model':}}
|
{{if request.function=='d3_graph_model':}}
|
||||||
<h2>{{=T("Graph Model")}}</h2>
|
<h2>{{=T("Graph Model")}}</h2>
|
||||||
{{if not pgv:}}
|
{{if not databases:}}
|
||||||
{{=T('pygraphviz library not found')}}
|
|
||||||
{{elif not databases:}}
|
|
||||||
{{=T("No databases in this application")}}
|
{{=T("No databases in this application")}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<div class="btn-group">
|
<div id="vis"></div>
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||||
<i class="icon-download"></i> {{=T('Save model as...')}}
|
<script>
|
||||||
<span class="caret"></span>
|
// Define the d3 input data
|
||||||
</a>
|
{{from gluon.serializers import json }}
|
||||||
<ul class="dropdown-menu">
|
var nodes = {{=XML(json(nodes))}};
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
var links = {{=XML(json(links))}};
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
d3_graph();
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
</script>
|
||||||
<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'))}}
|
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{if request.function == 'manage':}}
|
{{if request.function == 'manage':}}
|
||||||
<h2>{{=heading}}</h2>
|
<h2>{{=heading}}</h2>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.3.1.min.css')}}">
|
<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 -->
|
<!-- 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'}}
|
{{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):}}
|
{{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')}}
|
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
|
||||||
{{pass}}
|
{{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>
|
</div>
|
||||||
<ul class="unstyled act_edit">
|
<ul class="unstyled act_edit">
|
||||||
{{for m in models:}}
|
{{for m in models:}}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
<a class="btn dropdown-toggle" data-toggle="dropdown">
|
||||||
{{=T('Manage')}}
|
{{=T('Manage')}}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -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>
|
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
|
||||||
{{if len(response._vars)==1:}}
|
{{if len(response._vars)==1:}}
|
||||||
{{=BEAUTIFY(response._vars.values()[0])}}
|
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
|
||||||
{{elif len(response._vars)>1:}}
|
{{elif len(response._vars)>1:}}
|
||||||
{{=BEAUTIFY(response._vars)}}
|
{{=BEAUTIFY(response._vars)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<link rel="stylesheet" href="{{=URL('static','plugin_jqmobile/jquery.mobile-1.3.1.min.css')}}">
|
<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 -->
|
<!-- 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'}}
|
{{include 'web2py_ajax.html'}}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ import gluon.contenttype
|
|||||||
import gluon.fileutils
|
import gluon.fileutils
|
||||||
from gluon._compat import iteritems
|
from gluon._compat import iteritems
|
||||||
|
|
||||||
try:
|
|
||||||
import pygraphviz as pgv
|
|
||||||
except ImportError:
|
|
||||||
pgv = None
|
|
||||||
|
|
||||||
is_gae = request.env.web2py_runtime_gae or False
|
is_gae = request.env.web2py_runtime_gae or False
|
||||||
|
|
||||||
# ## critical --- make a copy of the environment
|
# ## critical --- make a copy of the environment
|
||||||
@@ -465,6 +460,7 @@ def ccache():
|
|||||||
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
||||||
|
|
||||||
for key in cache.disk.storage:
|
for key in cache.disk.storage:
|
||||||
|
value = cache.disk.storage[key]
|
||||||
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
|
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
|
||||||
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
||||||
disk['misses'] = value[1]['misses']
|
disk['misses'] = value[1]['misses']
|
||||||
@@ -564,57 +560,6 @@ def table_template(table):
|
|||||||
_cellborder=0, _cellspacing=0)
|
_cellborder=0, _cellspacing=0)
|
||||||
).xml()
|
).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():
|
def manage():
|
||||||
tables = manager_action['tables']
|
tables = manager_action['tables']
|
||||||
if isinstance(tables[0], str):
|
if isinstance(tables[0], str):
|
||||||
@@ -699,3 +644,52 @@ 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_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)
|
ul_main.append(ul_t)
|
||||||
return ul_main
|
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"
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
nodes = []
|
||||||
|
links = []
|
||||||
|
|
||||||
|
subgraphs = dict()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
+4
-5
File diff suppressed because one or more lines are too long
@@ -38,8 +38,12 @@
|
|||||||
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
||||||
else $('#' + id).show().fadeOut('slow');
|
else $('#' + id).show().fadeOut('slow');
|
||||||
},
|
},
|
||||||
ajax: function (u, s, t) {
|
ajax: function (u, s, t, options) {
|
||||||
/*simple ajax function*/
|
/*simple ajax function*/
|
||||||
|
|
||||||
|
// set options default value
|
||||||
|
options = typeof options !== 'undefined' ? options : {};
|
||||||
|
|
||||||
var query = '';
|
var query = '';
|
||||||
if (typeof s == 'string') {
|
if (typeof s == 'string') {
|
||||||
var d = $(s).serialize();
|
var d = $(s).serialize();
|
||||||
@@ -59,18 +63,44 @@
|
|||||||
query = pcs.join('&');
|
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',
|
type: 'POST',
|
||||||
url: u,
|
url: u,
|
||||||
data: query,
|
data: query,
|
||||||
success: function (msg) {
|
success: success
|
||||||
if (t) {
|
};
|
||||||
if (t == ':eval') eval(msg);
|
|
||||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
//remove custom "done" option if exists
|
||||||
else t(msg);
|
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) {
|
ajax_fields: function (target) {
|
||||||
/*
|
/*
|
||||||
@@ -168,7 +198,8 @@
|
|||||||
* and require no dom manipulations
|
* and require no dom manipulations
|
||||||
*/
|
*/
|
||||||
var doc = $(document);
|
var doc = $(document);
|
||||||
doc.on('click', '.w2p_flash', function () {
|
doc.on('click', '.w2p_flash', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
var t = $(this);
|
var t = $(this);
|
||||||
if (t.css('top') == '0px') t.slideUp('slow');
|
if (t.css('top') == '0px') t.slideUp('slow');
|
||||||
else t.fadeOut();
|
else t.fadeOut();
|
||||||
@@ -316,6 +347,7 @@
|
|||||||
'beforeSend': function (xhr, settings) {
|
'beforeSend': function (xhr, settings) {
|
||||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||||
xhr.setRequestHeader('web2py-component-element', target);
|
xhr.setRequestHeader('web2py-component-element', target);
|
||||||
|
web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
|
||||||
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
|
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
|
||||||
},
|
},
|
||||||
'success': function (data, status, xhr) {
|
'success': function (data, status, xhr) {
|
||||||
|
|||||||
@@ -233,30 +233,22 @@
|
|||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{if request.function=='graph_model':}}
|
{{if request.function=='d3_graph_model':}}
|
||||||
<h2>{{=T("Graph Model")}}</h2>
|
<h2>{{=T("Graph Model")}}</h2>
|
||||||
{{if not pgv:}}
|
{{if not databases:}}
|
||||||
{{=T('pygraphviz library not found')}}
|
|
||||||
{{elif not databases:}}
|
|
||||||
{{=T("No databases in this application")}}
|
{{=T("No databases in this application")}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<div class="btn-group">
|
<div id="vis"></div>
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||||
<i class="icon-download"></i> {{=T('Save model as...')}}
|
<script>
|
||||||
<span class="caret"></span>
|
// Define the d3 input data
|
||||||
</a>
|
{{from gluon.serializers import json }}
|
||||||
<ul class="dropdown-menu">
|
var nodes = {{=XML(json(nodes))}};
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
var links = {{=XML(json(links))}};
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
d3_graph();
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
</script>
|
||||||
<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'))}}
|
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{if request.function == 'manage':}}
|
{{if request.function == 'manage':}}
|
||||||
<h2>{{=heading}}</h2>
|
<h2>{{=heading}}</h2>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
|
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_win.zip">For Windows</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
|
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_win.zip">For Windows</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
|
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
|
||||||
@@ -28,19 +28,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
|
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_osx.zip">For Mac</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
|
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded green" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
|
<a class="btn btn180 rounded green" href="https://mdipierro.pythonanywhere.com/examples/static/web2py_src.zip">Source Code</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
|
<a class="btn btn180 rounded yellow" href="https://mdipierro.pythonanywhere.com/examples/static/nightly/web2py_src.zip">Source Code</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn180 rounded red" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a>
|
<a class="btn btn180 rounded red" href="http://web2py.readthedocs.org/en/latest/">Source code docs</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>
|
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
|
||||||
{{if len(response._vars)==1:}}
|
{{if len(response._vars)==1:}}
|
||||||
{{=BEAUTIFY(response._vars.values()[0])}}
|
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
|
||||||
{{elif len(response._vars)>1:}}
|
{{elif len(response._vars)>1:}}
|
||||||
{{=BEAUTIFY(response._vars)}}
|
{{=BEAUTIFY(response._vars)}}
|
||||||
{{pass}}
|
{{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
|
import gluon.fileutils
|
||||||
from gluon._compat import iteritems
|
from gluon._compat import iteritems
|
||||||
|
|
||||||
try:
|
|
||||||
import pygraphviz as pgv
|
|
||||||
except ImportError:
|
|
||||||
pgv = None
|
|
||||||
|
|
||||||
is_gae = request.env.web2py_runtime_gae or False
|
is_gae = request.env.web2py_runtime_gae or False
|
||||||
|
|
||||||
# ## critical --- make a copy of the environment
|
# ## critical --- make a copy of the environment
|
||||||
@@ -465,6 +460,7 @@ def ccache():
|
|||||||
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
||||||
|
|
||||||
for key in cache.disk.storage:
|
for key in cache.disk.storage:
|
||||||
|
value = cache.disk.storage[key]
|
||||||
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
|
if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
|
||||||
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
||||||
disk['misses'] = value[1]['misses']
|
disk['misses'] = value[1]['misses']
|
||||||
@@ -564,57 +560,6 @@ def table_template(table):
|
|||||||
_cellborder=0, _cellspacing=0)
|
_cellborder=0, _cellspacing=0)
|
||||||
).xml()
|
).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():
|
def manage():
|
||||||
tables = manager_action['tables']
|
tables = manager_action['tables']
|
||||||
if isinstance(tables[0], str):
|
if isinstance(tables[0], str):
|
||||||
@@ -699,3 +644,52 @@ 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_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)
|
ul_main.append(ul_t)
|
||||||
return ul_main
|
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"
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
nodes = []
|
||||||
|
links = []
|
||||||
|
|
||||||
|
subgraphs = dict()
|
||||||
|
|
||||||
|
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,8 +3,8 @@
|
|||||||
'!langcode!': 'fr-ca',
|
'!langcode!': 'fr-ca',
|
||||||
'!langname!': 'Français (Canadien)',
|
'!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',
|
'"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} deleted': '%s lignes supprimées',
|
||||||
'%s %%{row} updated': '%s rangées mises à jour',
|
'%s %%{row} updated': '%s lignes mises à jour',
|
||||||
'%s selected': '%s sélectionné',
|
'%s selected': '%s sélectionné',
|
||||||
'%Y-%m-%d': '%Y-%m-%d',
|
'%Y-%m-%d': '%Y-%m-%d',
|
||||||
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
|
'%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)}',
|
'**%(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)',
|
'**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)',
|
'``**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',
|
||||||
'About': 'À propos',
|
'About': 'À propos',
|
||||||
'Access Control': "Contrôle d'accès",
|
'Access Control': "Contrôle d'accès",
|
||||||
'admin': 'admin',
|
'admin': 'admin',
|
||||||
'Administrative Interface': 'Administrative Interface',
|
'Administrative Interface': "Interface d'administration",
|
||||||
'Administrative interface': "Interface d'administration",
|
'Administrative interface': "Interface d'administration",
|
||||||
'Ajax Recipes': 'Recettes Ajax',
|
'Ajax Recipes': 'Recettes Ajax',
|
||||||
'An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
'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',
|
'change password': 'changer le mot de passe',
|
||||||
'Check to delete': 'Cliquez pour supprimer',
|
'Check to delete': 'Cliquez pour supprimer',
|
||||||
'Check to delete:': 'Cliquez pour supprimer:',
|
'Check to delete:': 'Cliquez pour supprimer:',
|
||||||
'Clear CACHE?': 'Clear CACHE?',
|
'Clear CACHE?': 'Vider le CACHE?',
|
||||||
'Clear DISK': 'Clear DISK',
|
'Clear DISK': 'Vider le DISQUE',
|
||||||
'Clear RAM': 'Clear RAM',
|
'Clear RAM': 'Vider la RAM',
|
||||||
'Client IP': 'IP client',
|
'Client IP': 'IP client',
|
||||||
'Community': 'Communauté',
|
'Community': 'Communauté',
|
||||||
'Components and Plugins': 'Components and Plugins',
|
'Components and Plugins': 'Composants et Plugiciels',
|
||||||
'Config.ini': 'Config.ini',
|
'Config.ini': 'Config.ini',
|
||||||
'Controller': 'Contrôleur',
|
'Controller': 'Contrôleur',
|
||||||
'Copyright': "Droit d'auteur",
|
'Copyright': "Droit d'auteur",
|
||||||
|
'Created By': 'Créé par',
|
||||||
|
'Created On': 'Créé le',
|
||||||
'Current request': 'Demande actuelle',
|
'Current request': 'Demande actuelle',
|
||||||
'Current response': 'Réponse actuelle',
|
'Current response': 'Réponse actuelle',
|
||||||
'Current session': 'Session en cours',
|
'Current session': 'Session en cours',
|
||||||
'customize me!': 'personnalisez-moi!',
|
'customize me!': 'personnalisez-moi!',
|
||||||
'data uploaded': 'données téléchargées',
|
'data uploaded': 'données téléchargées',
|
||||||
'Database': 'base de donné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)',
|
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
||||||
'db': 'db',
|
'db': 'db',
|
||||||
'DB Model': 'Modèle DB',
|
'DB Model': 'Modèle BD',
|
||||||
'Delete:': 'Supprimer:',
|
'Delete:': 'Supprimer:',
|
||||||
'Demo': 'Démo',
|
'Demo': 'Démo',
|
||||||
'Deployment Recipes': 'Recettes de déploiement ',
|
'Deployment Recipes': 'Recettes de déploiement',
|
||||||
'Description': 'Descriptif',
|
'Description': 'Description',
|
||||||
'design': 'design',
|
'design': 'design',
|
||||||
'Design': 'Design',
|
'Design': 'Design',
|
||||||
'DISK': 'DISK',
|
'DISK': 'DISQUE',
|
||||||
'Disk Cache Keys': 'Disk Cache Keys',
|
'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.',
|
'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',
|
'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!',
|
'done!': 'fait!',
|
||||||
'Download': 'Téléchargement',
|
'Download': 'Téléchargement',
|
||||||
'E-mail': 'Courriel',
|
'E-mail': 'Courriel',
|
||||||
@@ -75,26 +78,27 @@
|
|||||||
'Edit current record': "Modifier l'enregistrement courant",
|
'Edit current record': "Modifier l'enregistrement courant",
|
||||||
'edit profile': 'modifier le profil',
|
'edit profile': 'modifier le profil',
|
||||||
'Edit This App': 'Modifier cette application',
|
'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',
|
'enter an integer between %(min)g and %(max)g': 'entrer un entier compris entre %(min)g et %(max)g',
|
||||||
'Errors': 'Erreurs',
|
'Errors': 'Erreurs',
|
||||||
'export as csv file': 'exporter sous forme de fichier csv',
|
'export as csv file': 'exporter sous forme de fichier csv',
|
||||||
'FAQ': 'faq',
|
'FAQ': 'FAQ',
|
||||||
'First name': 'Prénom',
|
'First name': 'Prénom',
|
||||||
'Forms and Validators': 'Formulaires et Validateurs',
|
'Forms and Validators': 'Formulaires et Validateurs',
|
||||||
'Free Applications': 'Applications gratuites',
|
'Free Applications': 'Applications gratuites',
|
||||||
'Function disabled': 'Fonction désactivée',
|
'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 %(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",
|
'Group uniquely assigned to user %(id)s': "Groupe unique attribué à l'utilisateur %(id)s",
|
||||||
'Groups': 'Groupes',
|
'Groups': 'Groupes',
|
||||||
'Hello World': 'Bonjour le monde',
|
'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)})',
|
'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',
|
'Home': 'Accueil',
|
||||||
'How did you get here?': 'How did you get here?',
|
'How did you get here?': 'How did you get here?',
|
||||||
'import': 'import',
|
'import': 'importer',
|
||||||
'Import/Export': 'Importer/Exporter',
|
'Import/Export': 'Importer/Exporter',
|
||||||
'Index': 'Index',
|
'Index': 'Index',
|
||||||
'insert new': 'insérer un nouveau',
|
'insert new': 'insérer un nouveau',
|
||||||
@@ -104,40 +108,47 @@
|
|||||||
'Invalid email': 'Courriel invalide',
|
'Invalid email': 'Courriel invalide',
|
||||||
'Invalid Query': 'Requête Invalide',
|
'Invalid Query': 'Requête Invalide',
|
||||||
'invalid request': 'requête invalide',
|
'invalid request': 'requête invalide',
|
||||||
'Key': 'Key',
|
'Is Active': 'Est actif',
|
||||||
|
'Key': 'Clé',
|
||||||
'Last name': 'Nom',
|
'Last name': 'Nom',
|
||||||
'Layout': 'Mise en page',
|
'Layout': 'Mise en page',
|
||||||
'Layout Plugins': 'Layout Plugins',
|
'Layout Plugins': 'Plugins de mise en page',
|
||||||
'Layouts': 'layouts',
|
'Layouts': 'Mises en page',
|
||||||
'Live chat': 'Clavardage en direct',
|
'Live chat': 'Clavardage en direct',
|
||||||
'Live Chat': 'Live Chat',
|
'Live Chat': 'Clavardage en direct',
|
||||||
'Log In': 'Log In',
|
'Loading...': 'Chargement...',
|
||||||
|
'loading...': 'chargement...',
|
||||||
|
'Log In': 'Connexion',
|
||||||
'Logged in': 'Connecté',
|
'Logged in': 'Connecté',
|
||||||
'login': 'connectez-vous',
|
'login': 'connexion',
|
||||||
'Login': 'Connectez-vous',
|
'Login': 'Connexion',
|
||||||
'logout': 'déconnectez-vous',
|
'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',
|
||||||
|
'Lost password?': 'Mot de passe perdu?',
|
||||||
'lost password?': 'mot de passe perdu?',
|
'lost password?': 'mot de passe perdu?',
|
||||||
'Main Menu': 'Menu principal',
|
'Main Menu': 'Menu principal',
|
||||||
'Manage %(action)s': 'Manage %(action)s',
|
'Manage %(action)s': 'Manage %(action)s',
|
||||||
'Manage Access Control': 'Manage Access Control',
|
'Manage Access Control': 'Manage Access Control',
|
||||||
'Manage Cache': 'Manage Cache',
|
'Manage Cache': 'Gérer le Cache',
|
||||||
'Memberships': 'Memberships',
|
'Memberships': 'Memberships',
|
||||||
'Menu Model': 'Menu modèle',
|
'Menu Model': 'Menu modèle',
|
||||||
'My Sites': 'My Sites',
|
'Modified By': 'Modifié par',
|
||||||
|
'Modified On': 'Modifié le',
|
||||||
|
'My Sites': 'Mes sites',
|
||||||
'Name': 'Nom',
|
'Name': 'Nom',
|
||||||
'New Record': 'Nouvel enregistrement',
|
'New Record': 'Nouvel enregistrement',
|
||||||
'new record inserted': 'nouvel enregistrement inséré',
|
'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',
|
'next 100 rows': '100 prochaines lignes',
|
||||||
'No databases in this application': "Cette application n'a pas de bases de données",
|
'No databases in this application': "Cette application n'a pas de bases de données",
|
||||||
'Number of entries: **%s**': 'Number of entries: **%s**',
|
'Number of entries: **%s**': 'Number of entries: **%s**',
|
||||||
|
'Object or table name': 'Objet ou nom de table',
|
||||||
'Online book': 'Online book',
|
'Online book': 'Online book',
|
||||||
'Online examples': 'Exemples en ligne',
|
'Online examples': 'Exemples en ligne',
|
||||||
'or import from csv file': "ou importer d'un fichier CSV",
|
'or import from csv file': "ou importer d'un fichier CSV",
|
||||||
'Origin': 'Origine',
|
'Origin': 'Origine',
|
||||||
'Other Plugins': 'Other Plugins',
|
'Other Plugins': 'Autres Plugiciels',
|
||||||
'Other Recipes': 'Autres recettes',
|
'Other Recipes': 'Autres recettes',
|
||||||
'Overview': 'Présentation',
|
'Overview': 'Présentation',
|
||||||
'password': 'mot de passe',
|
'password': 'mot de passe',
|
||||||
@@ -145,33 +156,34 @@
|
|||||||
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
||||||
'Permission': 'Permission',
|
'Permission': 'Permission',
|
||||||
'Permissions': 'Permissions',
|
'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',
|
'Plugins': 'Plugiciels',
|
||||||
'Powered by': 'Alimenté par',
|
'Powered by': 'Alimenté par',
|
||||||
'Preface': 'Préface',
|
'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',
|
'previous 100 rows': '100 lignes précédentes',
|
||||||
'profile': 'profile',
|
'profile': 'profil',
|
||||||
'pygraphviz library not found': 'pygraphviz library not found',
|
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
|
||||||
'Python': 'Python',
|
'Python': 'Python',
|
||||||
'Query:': 'Requête:',
|
'Query:': 'Requête:',
|
||||||
'Quick Examples': 'Examples Rapides',
|
'Quick Examples': 'Exemples Rapides',
|
||||||
'RAM': 'RAM',
|
'RAM': 'RAM',
|
||||||
'RAM Cache Keys': 'RAM Cache Keys',
|
'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.',
|
'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',
|
'Readme': 'Lisez-moi',
|
||||||
'Recipes': 'Recettes',
|
'Recipes': 'Recettes',
|
||||||
'Record': 'enregistrement',
|
'Record': 'enregistrement',
|
||||||
'Record %(id)s created': 'Record %(id)s created',
|
'Record %(id)s created': 'Enregistrement %(id)s créé',
|
||||||
'Record %(id)s updated': 'Record %(id)s updated',
|
'Record %(id)s updated': 'Enregistrement %(id)s modifié',
|
||||||
'Record Created': 'Record Created',
|
'Record Created': 'Enregistrement créé',
|
||||||
'record does not exist': "l'archive n'existe pas",
|
'record does not exist': "l'archive n'existe pas",
|
||||||
'Record ID': "ID d'enregistrement",
|
'Record ID': "ID de l'enregistrement",
|
||||||
'Record id': "id d'enregistrement",
|
'Record id': "id de l'enregistrement",
|
||||||
'Record Updated': 'Record Updated',
|
'Record Updated': 'Enregistrement modifié',
|
||||||
'Register': "S'inscrire",
|
'Register': "S'inscrire",
|
||||||
'register': "s'inscrire",
|
'register': "s'inscrire",
|
||||||
|
'Registration identifier': "Identifiant d'inscription",
|
||||||
'Registration key': "Clé d'enregistrement",
|
'Registration key': "Clé d'enregistrement",
|
||||||
'Registration successful': 'Inscription réussie',
|
'Registration successful': 'Inscription réussie',
|
||||||
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
||||||
@@ -179,18 +191,18 @@
|
|||||||
'Reset Password key': 'Réinitialiser le mot clé',
|
'Reset Password key': 'Réinitialiser le mot clé',
|
||||||
'Resources': 'Ressources',
|
'Resources': 'Ressources',
|
||||||
'Role': 'Rôle',
|
'Role': 'Rôle',
|
||||||
'Roles': 'Roles',
|
'Roles': 'Rôles',
|
||||||
'Rows in Table': 'Lignes du tableau',
|
'Rows in Table': 'Lignes du tableau',
|
||||||
'Rows selected': 'Lignes sélectionnées',
|
'Rows selected': 'Lignes sélectionnées',
|
||||||
'Save model as...': 'Save model as...',
|
'Save model as...': 'Enregistrer le modèle sous...',
|
||||||
'Semantic': 'Sémantique',
|
'Semantic': 'Sémantique',
|
||||||
'Services': 'Services',
|
'Services': 'Services',
|
||||||
'Sign Up': 'Sign Up',
|
'Sign Up': "S'inscrire",
|
||||||
'Size of cache:': 'Size of cache:',
|
'Size of cache:': 'Taille de la mémoire cache:',
|
||||||
'state': 'état',
|
'state': 'état',
|
||||||
'Statistics': 'Statistics',
|
'Statistics': 'Statistiques',
|
||||||
'Stylesheet': 'Feuille de style',
|
'Stylesheet': 'Feuille de style',
|
||||||
'submit': 'submit',
|
'submit': 'soumettre',
|
||||||
'Submit': 'Soumettre',
|
'Submit': 'Soumettre',
|
||||||
'Support': 'Soutien',
|
'Support': 'Soutien',
|
||||||
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||||
@@ -202,31 +214,31 @@
|
|||||||
'The Views': 'Les Vues',
|
'The Views': 'Les Vues',
|
||||||
'This App': 'Cette Appli',
|
'This App': 'Cette Appli',
|
||||||
'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage",
|
'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',
|
'Timestamp': 'Horodatage',
|
||||||
'Traceback': 'Traceback',
|
'Traceback': 'Traceback',
|
||||||
'Twitter': 'Twitter',
|
'Twitter': 'Twitter',
|
||||||
'unable to parse csv file': "incapable d'analyser le fichier cvs",
|
'unable to parse csv file': "incapable d'analyser le fichier cvs",
|
||||||
'Update:': 'Mise à jour:',
|
'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.',
|
'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': 'User',
|
'User': 'Utilisateur',
|
||||||
'User %(id)s Logged-in': 'Utilisateur %(id)s connecté',
|
'User %(id)s Logged-in': 'Utilisateur %(id)s connecté',
|
||||||
'User %(id)s Registered': 'Utilisateur %(id)s enregistré',
|
'User %(id)s Registered': 'Utilisateur %(id)s enregistré',
|
||||||
'User ID': 'ID utilisateur',
|
'User ID': 'ID utilisateur',
|
||||||
'User Voice': 'User Voice',
|
'User Voice': "Voix de l'utilisateur",
|
||||||
'Users': 'Users',
|
'Users': 'Utilisateurs',
|
||||||
'value already in database or empty': 'valeur déjà dans la base ou vide',
|
'value already in database or empty': 'valeur déjà dans la base ou inexistante',
|
||||||
'Verify Password': 'Vérifiez le mot de passe',
|
'Verify Password': 'Vérifiez le mot de passe',
|
||||||
'Videos': 'Vidéos',
|
'Videos': 'Vidéos',
|
||||||
'View': 'Présentation',
|
'View': 'Vue',
|
||||||
'Web2py': 'Web2py',
|
'Web2py': 'Web2py',
|
||||||
'Welcome': 'Bienvenu',
|
'Welcome': 'Bienvenue',
|
||||||
'Welcome %s': 'Bienvenue %s',
|
'Welcome %s': 'Bienvenue %s',
|
||||||
'Welcome to web2py': 'Bienvenue à web2py',
|
'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',
|
'Which called the function %s located in the file %s': 'Qui a appelé la fonction %s se trouvant dans le fichier %s',
|
||||||
'Working...': 'Working...',
|
'Working...': 'Traitement en cours...',
|
||||||
'You are successfully running web2py': 'Vous roulez avec succès web2py',
|
'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 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",
|
'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)}',
|
'**%(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)',
|
'**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)',
|
'``**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',
|
'About': 'À propos',
|
||||||
'Access Control': "Contrôle d'accès",
|
'Access Control': "Contrôle d'accès",
|
||||||
'admin': 'admin',
|
'admin': 'admin',
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
'Cache': 'Cache',
|
'Cache': 'Cache',
|
||||||
'Cache Cleared': 'Cache Cleared',
|
'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 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',
|
'Cannot be empty': 'Ne peut pas être vide',
|
||||||
'change password': 'changer le mot de passe',
|
'change password': 'changer le mot de passe',
|
||||||
'Check to delete': 'Cliquez pour supprimer',
|
'Check to delete': 'Cliquez pour supprimer',
|
||||||
@@ -41,10 +43,10 @@
|
|||||||
'Clear RAM': 'Vider la RAM',
|
'Clear RAM': 'Vider la RAM',
|
||||||
'Client IP': 'IP client',
|
'Client IP': 'IP client',
|
||||||
'Community': 'Communauté',
|
'Community': 'Communauté',
|
||||||
'Components and Plugins': 'Composants et Plugins',
|
'Components and Plugins': 'Composants et Plugiciels',
|
||||||
'Config.ini': 'Config.ini',
|
'Config.ini': 'Config.ini',
|
||||||
'Controller': 'Contrôleur',
|
'Controller': 'Contrôleur',
|
||||||
'Copyright': 'Copyright',
|
'Copyright': "Droit d'auteur",
|
||||||
'Created By': 'Créé par',
|
'Created By': 'Créé par',
|
||||||
'Created On': 'Créé le',
|
'Created On': 'Créé le',
|
||||||
'Current request': 'Demande actuelle',
|
'Current request': 'Demande actuelle',
|
||||||
@@ -55,8 +57,8 @@
|
|||||||
'Database': 'base de données',
|
'Database': 'base de données',
|
||||||
'Database %s select': 'base de données %s selectionnée',
|
'Database %s select': 'base de données %s selectionnée',
|
||||||
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
'Database Administration (appadmin)': 'Database Administration (appadmin)',
|
||||||
'db': 'bdd',
|
'db': 'db',
|
||||||
'DB Model': 'Modèle BDD',
|
'DB Model': 'Modèle BD',
|
||||||
'Delete:': 'Supprimer:',
|
'Delete:': 'Supprimer:',
|
||||||
'Demo': 'Démo',
|
'Demo': 'Démo',
|
||||||
'Deployment Recipes': 'Recettes de déploiement',
|
'Deployment Recipes': 'Recettes de déploiement',
|
||||||
@@ -71,12 +73,13 @@
|
|||||||
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
|
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
|
||||||
'done!': 'fait!',
|
'done!': 'fait!',
|
||||||
'Download': 'Téléchargement',
|
'Download': 'Téléchargement',
|
||||||
'E-mail': 'E-mail',
|
'E-mail': 'Courriel',
|
||||||
'Edit': 'Éditer',
|
'Edit': 'Éditer',
|
||||||
'Edit current record': "Modifier l'enregistrement courant",
|
'Edit current record': "Modifier l'enregistrement courant",
|
||||||
'edit profile': 'modifier le profil',
|
'edit profile': 'modifier le profil',
|
||||||
'Edit This App': 'Modifier cette application',
|
'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',
|
'enter an integer between %(min)g and %(max)g': 'entrez un entier entre %(min)g et %(max)g',
|
||||||
'Errors': 'Erreurs',
|
'Errors': 'Erreurs',
|
||||||
'export as csv file': 'exporter sous forme de fichier csv',
|
'export as csv file': 'exporter sous forme de fichier csv',
|
||||||
@@ -85,22 +88,24 @@
|
|||||||
'Forms and Validators': 'Formulaires et Validateurs',
|
'Forms and Validators': 'Formulaires et Validateurs',
|
||||||
'Free Applications': 'Applications gratuites',
|
'Free Applications': 'Applications gratuites',
|
||||||
'Function disabled': 'Fonction désactivée',
|
'Function disabled': 'Fonction désactivée',
|
||||||
'Graph Model': 'Graph Model',
|
'Graph Model': 'Représentation graphique du modèle',
|
||||||
'Group ID': 'Groupe ID',
|
'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',
|
'Groups': 'Groupes',
|
||||||
'Hello World': 'Bonjour le monde',
|
'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)})',
|
'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',
|
'Home': 'Accueil',
|
||||||
'How did you get here?': 'Comment êtes-vous arrivé ici?',
|
'How did you get here?': 'How did you get here?',
|
||||||
'import': 'Importer',
|
'import': 'importer',
|
||||||
'Import/Export': 'Importer/Exporter',
|
'Import/Export': 'Importer/Exporter',
|
||||||
'Index': 'Index',
|
'Index': 'Index',
|
||||||
'insert new': 'insérer un nouveau',
|
'insert new': 'insérer un nouveau',
|
||||||
'insert new %s': 'insérer un nouveau %s',
|
'insert new %s': 'insérer un nouveau %s',
|
||||||
'Internal State': 'État interne',
|
'Internal State': 'État interne',
|
||||||
'Introduction': 'Introduction',
|
'Introduction': 'Présentation',
|
||||||
'Invalid email': 'E-mail invalide',
|
'Invalid email': 'Courriel invalide',
|
||||||
'Invalid Query': 'Requête Invalide',
|
'Invalid Query': 'Requête Invalide',
|
||||||
'invalid request': 'requête invalide',
|
'invalid request': 'requête invalide',
|
||||||
'Is Active': 'Est actif',
|
'Is Active': 'Est actif',
|
||||||
@@ -109,12 +114,15 @@
|
|||||||
'Layout': 'Mise en page',
|
'Layout': 'Mise en page',
|
||||||
'Layout Plugins': 'Plugins de mise en page',
|
'Layout Plugins': 'Plugins de mise en page',
|
||||||
'Layouts': 'Mises en page',
|
'Layouts': 'Mises en page',
|
||||||
'Live chat': 'Chat en direct',
|
'Live chat': 'Clavardage en direct',
|
||||||
'Live Chat': 'Chat en direct',
|
'Live Chat': 'Clavardage en direct',
|
||||||
'Log In': 'Log In',
|
'Loading...': 'Chargement...',
|
||||||
'login': 'connectez-vous',
|
'loading...': 'chargement...',
|
||||||
'Login': 'Connectez-vous',
|
'Log In': 'Connexion',
|
||||||
'logout': 'déconnectez-vous',
|
'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',
|
'Lost Password': 'Mot de passe perdu',
|
||||||
'Lost password?': 'Mot de passe perdu?',
|
'Lost password?': 'Mot de passe perdu?',
|
||||||
@@ -131,7 +139,7 @@
|
|||||||
'Name': 'Nom',
|
'Name': 'Nom',
|
||||||
'New Record': 'Nouvel enregistrement',
|
'New Record': 'Nouvel enregistrement',
|
||||||
'new record inserted': 'nouvel enregistrement inséré',
|
'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',
|
'next 100 rows': '100 prochaines lignes',
|
||||||
'No databases in this application': "Cette application n'a pas de bases de données",
|
'No databases in this application': "Cette application n'a pas de bases de données",
|
||||||
'Number of entries: **%s**': 'Number of entries: **%s**',
|
'Number of entries: **%s**': 'Number of entries: **%s**',
|
||||||
@@ -140,55 +148,63 @@
|
|||||||
'Online examples': 'Exemples en ligne',
|
'Online examples': 'Exemples en ligne',
|
||||||
'or import from csv file': "ou importer d'un fichier CSV",
|
'or import from csv file': "ou importer d'un fichier CSV",
|
||||||
'Origin': 'Origine',
|
'Origin': 'Origine',
|
||||||
'Other Plugins': 'Autres Plugins',
|
'Other Plugins': 'Autres Plugiciels',
|
||||||
'Other Recipes': 'Autres recettes',
|
'Other Recipes': 'Autres recettes',
|
||||||
'Overview': 'Présentation',
|
'Overview': 'Présentation',
|
||||||
|
'password': 'mot de passe',
|
||||||
'Password': 'Mot de passe',
|
'Password': 'Mot de passe',
|
||||||
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
"Password fields don't match": 'Les mots de passe ne correspondent pas',
|
||||||
'Permission': 'Permission',
|
'Permission': 'Permission',
|
||||||
'Permissions': 'Permissions',
|
'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',
|
'Powered by': 'Alimenté par',
|
||||||
'Preface': 'Préface',
|
'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',
|
'previous 100 rows': '100 lignes précédentes',
|
||||||
|
'profile': 'profil',
|
||||||
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
|
'pygraphviz library not found': 'Bibliothèque pygraphviz introuvable',
|
||||||
'Python': 'Python',
|
'Python': 'Python',
|
||||||
'Query:': 'Requête:',
|
'Query:': 'Requête:',
|
||||||
'Quick Examples': 'Exemples Rapides',
|
'Quick Examples': 'Exemples Rapides',
|
||||||
'RAM': 'RAM',
|
'RAM': 'RAM',
|
||||||
'RAM Cache Keys': 'Clés de cache de la RAM',
|
'RAM Cache Keys': 'RAM Cache Keys',
|
||||||
'Ram Cleared': 'Ram vidée',
|
'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.',
|
'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',
|
'Readme': 'Lisez-moi',
|
||||||
'Recipes': 'Recettes',
|
'Recipes': 'Recettes',
|
||||||
'Record': 'enregistrement',
|
'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 does not exist': "l'archive n'existe pas",
|
||||||
'Record ID': "ID d'enregistrement",
|
'Record ID': "ID de l'enregistrement",
|
||||||
'Record id': "id d'enregistrement",
|
'Record id': "id de l'enregistrement",
|
||||||
|
'Record Updated': 'Enregistrement modifié',
|
||||||
'Register': "S'inscrire",
|
'Register': "S'inscrire",
|
||||||
'register': "s'inscrire",
|
'register': "s'inscrire",
|
||||||
'Registration identifier': "Identifiant d'enregistrement",
|
'Registration identifier': "Identifiant d'inscription",
|
||||||
'Registration key': "Clé d'enregistrement",
|
'Registration key': "Clé d'enregistrement",
|
||||||
|
'Registration successful': 'Inscription réussie',
|
||||||
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
|
||||||
'Request reset password': 'Demande de réinitialiser le mot clé',
|
'Request reset password': 'Demande de réinitialiser le mot clé',
|
||||||
'Reset Password key': 'Réinitialiser le mot clé',
|
'Reset Password key': 'Réinitialiser le mot clé',
|
||||||
'Resources': 'Ressources',
|
'Resources': 'Ressources',
|
||||||
'Role': 'Rôle',
|
'Role': 'Rôle',
|
||||||
'Roles': 'Roles',
|
'Roles': 'Rôles',
|
||||||
'Rows in Table': 'Lignes du tableau',
|
'Rows in Table': 'Lignes du tableau',
|
||||||
'Rows selected': 'Lignes sélectionnées',
|
'Rows selected': 'Lignes sélectionnées',
|
||||||
'Save model as...': 'Enregistrer le modèle sous...',
|
'Save model as...': 'Enregistrer le modèle sous...',
|
||||||
'Semantic': 'Sémantique',
|
'Semantic': 'Sémantique',
|
||||||
'Services': 'Services',
|
'Services': 'Services',
|
||||||
'Sign Up': 'Sign Up',
|
'Sign Up': "S'inscrire",
|
||||||
'Size of cache:': 'Taille du cache:',
|
'Size of cache:': 'Taille de la mémoire cache:',
|
||||||
'state': 'état',
|
'state': 'état',
|
||||||
'Statistics': 'Statistiques',
|
'Statistics': 'Statistiques',
|
||||||
'Stylesheet': 'Feuille de style',
|
'Stylesheet': 'Feuille de style',
|
||||||
'submit': 'soumettre',
|
'submit': 'soumettre',
|
||||||
'Submit': 'Soumettre',
|
'Submit': 'Soumettre',
|
||||||
'Support': 'Support',
|
'Support': 'Soutien',
|
||||||
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
|
||||||
'Table': 'tableau',
|
'Table': 'tableau',
|
||||||
'Table name': 'Nom du tableau',
|
'Table name': 'Nom du tableau',
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
|
# YOU CAN COPY THIS FILE TO ANY APPLICATION'S ROOT DIRECTORY WITHOUT CHANGES!
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
from fileutils import abspath
|
from gluon.fileutils import abspath
|
||||||
from languages import read_possible_languages
|
from gluon.languages import read_possible_languages
|
||||||
|
|
||||||
possible_languages = read_possible_languages(abspath('applications', app))
|
possible_languages = read_possible_languages(abspath('applications', app))
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
+4
-5
File diff suppressed because one or more lines are too long
@@ -12,6 +12,8 @@
|
|||||||
$.error('web2py.js has already been loaded!');
|
$.error('web2py.js has already been loaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var FORMDATA_IS_SUPPORTED = typeof(FormData) !== 'undefined';
|
||||||
|
|
||||||
String.prototype.reverse = function () {
|
String.prototype.reverse = function () {
|
||||||
return this.split('').reverse().join('');
|
return this.split('').reverse().join('');
|
||||||
};
|
};
|
||||||
@@ -38,8 +40,12 @@
|
|||||||
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
if (value > 0) $('#' + id).hide().fadeIn('slow');
|
||||||
else $('#' + id).show().fadeOut('slow');
|
else $('#' + id).show().fadeOut('slow');
|
||||||
},
|
},
|
||||||
ajax: function (u, s, t) {
|
ajax: function (u, s, t, options) {
|
||||||
/*simple ajax function*/
|
/*simple ajax function*/
|
||||||
|
|
||||||
|
// set options default value
|
||||||
|
options = typeof options !== 'undefined' ? options : {};
|
||||||
|
|
||||||
var query = '';
|
var query = '';
|
||||||
if (typeof s == 'string') {
|
if (typeof s == 'string') {
|
||||||
var d = $(s).serialize();
|
var d = $(s).serialize();
|
||||||
@@ -59,18 +65,44 @@
|
|||||||
query = pcs.join('&');
|
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',
|
type: 'POST',
|
||||||
url: u,
|
url: u,
|
||||||
data: query,
|
data: query,
|
||||||
success: function (msg) {
|
success: success
|
||||||
if (t) {
|
};
|
||||||
if (t == ':eval') eval(msg);
|
|
||||||
else if (typeof t == 'string') $('#' + t).html(msg);
|
//remove custom "done" option if exists
|
||||||
else t(msg);
|
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) {
|
ajax_fields: function (target) {
|
||||||
/*
|
/*
|
||||||
@@ -168,7 +200,8 @@
|
|||||||
* and require no dom manipulations
|
* and require no dom manipulations
|
||||||
*/
|
*/
|
||||||
var doc = $(document);
|
var doc = $(document);
|
||||||
doc.on('click', '.w2p_flash', function () {
|
doc.on('click', '.w2p_flash', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
var t = $(this);
|
var t = $(this);
|
||||||
if (t.css('top') == '0px') t.slideUp('slow');
|
if (t.css('top') == '0px') t.slideUp('slow');
|
||||||
else t.fadeOut();
|
else t.fadeOut();
|
||||||
@@ -289,7 +322,15 @@
|
|||||||
form.submit(function (e) {
|
form.submit(function (e) {
|
||||||
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
web2py.disableElement(form.find(web2py.formInputClickSelector));
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
web2py.ajax_page('post', url, form.serialize(), target, form);
|
|
||||||
|
var formData;
|
||||||
|
if (FORMDATA_IS_SUPPORTED) {
|
||||||
|
formData = new FormData(form[0]); // Allows file uploads.
|
||||||
|
} else {
|
||||||
|
formData = form.serialize(); // Fallback for older browsers.
|
||||||
|
}
|
||||||
|
web2py.ajax_page('post', url, formData, target, form);
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
form.on('click', web2py.formInputClickSelector, function (e) {
|
form.on('click', web2py.formInputClickSelector, function (e) {
|
||||||
@@ -308,14 +349,22 @@
|
|||||||
if (web2py.isUndefined(element)) element = $(document);
|
if (web2py.isUndefined(element)) element = $(document);
|
||||||
/* if target is not there, fill it with something that there isn't in the page*/
|
/* if target is not there, fill it with something that there isn't in the page*/
|
||||||
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
if (web2py.isUndefined(target) || target === '') target = 'w2p_none';
|
||||||
|
|
||||||
|
/* processData and contentType must be set to false when passing a FormData
|
||||||
|
object to jQuery.ajax. */
|
||||||
|
var isFormData = Object.prototype.toString.call(data) === '[object FormData]';
|
||||||
|
var contentType = isFormData ? false : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||||
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
if (web2py.fire(element, 'ajax:before', null, target)) { /*test a usecase, should stop here if returns false */
|
||||||
$.ajax({
|
$.ajax({
|
||||||
'type': method,
|
'type': method,
|
||||||
'url': action,
|
'url': action,
|
||||||
'data': data,
|
'data': data,
|
||||||
|
'processData': !isFormData,
|
||||||
|
'contentType': contentType,
|
||||||
'beforeSend': function (xhr, settings) {
|
'beforeSend': function (xhr, settings) {
|
||||||
xhr.setRequestHeader('web2py-component-location', document.location);
|
xhr.setRequestHeader('web2py-component-location', document.location);
|
||||||
xhr.setRequestHeader('web2py-component-element', target);
|
xhr.setRequestHeader('web2py-component-element', target);
|
||||||
|
web2py.fire(element, 'w2p:componentBegin', [xhr, settings], target);
|
||||||
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
|
return web2py.fire(element, 'ajax:beforeSend', [xhr, settings], target); //test a usecase, should stop here if returns false
|
||||||
},
|
},
|
||||||
'success': function (data, status, xhr) {
|
'success': function (data, status, xhr) {
|
||||||
@@ -667,8 +716,9 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/* Disables form elements:
|
/* Disables form elements:
|
||||||
|
- Does not disable elements with 'data-w2p_disable' attribute
|
||||||
- Caches element value in 'w2p_enable_with' data store
|
- Caches element value in 'w2p_enable_with' data store
|
||||||
- Replaces element text with value of 'data-disable-with' attribute
|
- Replaces element text with value of 'data-w2p_disable_with' attribute
|
||||||
- Sets disabled property to true
|
- Sets disabled property to true
|
||||||
*/
|
*/
|
||||||
disableFormElements: function (form) {
|
disableFormElements: function (form) {
|
||||||
@@ -680,13 +730,15 @@
|
|||||||
if (!web2py.isUndefined(disable)) {
|
if (!web2py.isUndefined(disable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(disable_with)) {
|
if (!element.is(':file')) { // Altering file input values is not allowed.
|
||||||
element.data('w2p_disable_with', element[method]());
|
if (web2py.isUndefined(disable_with)) {
|
||||||
|
element.data('w2p_disable_with', element[method]());
|
||||||
|
}
|
||||||
|
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
||||||
|
element.data('w2p_enable_with', element[method]());
|
||||||
|
}
|
||||||
|
element[method](element.data('w2p_disable_with'));
|
||||||
}
|
}
|
||||||
if (web2py.isUndefined(element.data('w2p_enable_with'))) {
|
|
||||||
element.data('w2p_enable_with', element[method]());
|
|
||||||
}
|
|
||||||
element[method](element.data('w2p_disable_with'));
|
|
||||||
element.prop('disabled', true);
|
element.prop('disabled', true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -233,30 +233,22 @@
|
|||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{if request.function=='graph_model':}}
|
{{if request.function=='d3_graph_model':}}
|
||||||
<h2>{{=T("Graph Model")}}</h2>
|
<h2>{{=T("Graph Model")}}</h2>
|
||||||
{{if not pgv:}}
|
{{if not databases:}}
|
||||||
{{=T('pygraphviz library not found')}}
|
|
||||||
{{elif not databases:}}
|
|
||||||
{{=T("No databases in this application")}}
|
{{=T("No databases in this application")}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
<div class="btn-group">
|
<div id="vis"></div>
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
<link rel="stylesheet" href="{{=URL('admin','static','css/d3_graph.css')}}"/>
|
||||||
<i class="icon-download"></i> {{=T('Save model as...')}}
|
<script>
|
||||||
<span class="caret"></span>
|
// Define the d3 input data
|
||||||
</a>
|
{{from gluon.serializers import json }}
|
||||||
<ul class="dropdown-menu">
|
var nodes = {{=XML(json(nodes))}};
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
var links = {{=XML(json(links))}};
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
d3_graph();
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
</script>
|
||||||
<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'))}}
|
|
||||||
{{pass}}
|
{{pass}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
{{if request.function == 'manage':}}
|
{{if request.function == 'manage':}}
|
||||||
<h2>{{=heading}}</h2>
|
<h2>{{=heading}}</h2>
|
||||||
|
|||||||
@@ -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>
|
<h2>{{=' '.join(x.capitalize() for x in request.function.split('_'))}}</h2>
|
||||||
{{if len(response._vars)==1:}}
|
{{if len(response._vars)==1:}}
|
||||||
{{=BEAUTIFY(response._vars.values()[0])}}
|
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
|
||||||
{{elif len(response._vars)>1:}}
|
{{elif len(response._vars)>1:}}
|
||||||
{{=BEAUTIFY(response._vars)}}
|
{{=BEAUTIFY(response._vars)}}
|
||||||
{{pass}}
|
{{pass}}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# it is not safe to use as a generic.jsonp because of security implications.
|
# it is not safe to use as a generic.jsonp because of security implications.
|
||||||
|
|
||||||
if response.view == 'generic.jsonp':
|
if response.view == 'generic.jsonp':
|
||||||
raise HTTP(501,'generic.jsonp diasbled for security reasons')
|
raise HTTP(501,'generic.jsonp disabled for security reasons')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from gluon.serializers import json
|
from gluon.serializers import json
|
||||||
@@ -20,4 +20,4 @@ except ImportError:
|
|||||||
raise HTTP(405, 'JSON not available')
|
raise HTTP(405, 'JSON not available')
|
||||||
except:
|
except:
|
||||||
raise HTTP(405, 'JSON error')
|
raise HTTP(405, 'JSON error')
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ Notice:
|
|||||||
- no need to return a string
|
- no need to return a string
|
||||||
even if the function is called via ajax.
|
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}}
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{{=response.logo or ''}}
|
{{=response.logo or ''}}
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse navbar-collapse navbar-ex1-collapse">
|
<div class="collapse navbar-collapse">
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}}
|
{{='auth' in globals() and auth.navbar('Welcome',mode='dropdown') or ''}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,7 +1,8 @@
|
|||||||
from fabric.api import *
|
from fabric.api import *
|
||||||
from fabric.operations import put, get
|
from fabric.operations import put, get
|
||||||
from fabric.contrib.files import exists
|
from fabric.contrib.files import exists, append, uncomment
|
||||||
import os
|
import os
|
||||||
|
import crypt
|
||||||
import datetime
|
import datetime
|
||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
@@ -19,11 +20,11 @@ def create_user(username):
|
|||||||
"""fab -H root@host create_user:username"""
|
"""fab -H root@host create_user:username"""
|
||||||
password = getpass.getpass('password for %s> ' % username)
|
password = getpass.getpass('password for %s> ' % username)
|
||||||
run('useradd -m -G www-data -s /bin/bash -p %s %s' % (crypt.crypt(password, 'salt'), username))
|
run('useradd -m -G www-data -s /bin/bash -p %s %s' % (crypt.crypt(password, 'salt'), username))
|
||||||
|
local('ssh-copy-id %s' % env.hosts[0])
|
||||||
run('cp /etc/sudoers /tmp/sudoers.new')
|
run('cp /etc/sudoers /tmp/sudoers.new')
|
||||||
append('/tmp/sudoers.new', '%s ALL=(ALL) NOPASSWD:ALL' % username, use_sudo=True)
|
append('/tmp/sudoers.new', '%s ALL=(ALL) NOPASSWD:ALL' % username, use_sudo=True)
|
||||||
run('visudo -c -f /tmp/sudoers.new')
|
run('visudo -c -f /tmp/sudoers.new')
|
||||||
run('EDITOR="cp /tmp/sudoers.new" visudo')
|
run('EDITOR="cp /tmp/sudoers.new" visudo')
|
||||||
local('ssh-copy-id %s' % env.hosts[0])
|
|
||||||
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
|
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
|
||||||
|
|
||||||
def install_web2py():
|
def install_web2py():
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ if PY2:
|
|||||||
import cgi
|
import cgi
|
||||||
import cookielib
|
import cookielib
|
||||||
from xmlrpclib import ProtocolError
|
from xmlrpclib import ProtocolError
|
||||||
|
from gluon.contrib import ipaddress
|
||||||
BytesIO = StringIO
|
BytesIO = StringIO
|
||||||
reduce = reduce
|
reduce = reduce
|
||||||
hashlib_md5 = hashlib.md5
|
hashlib_md5 = hashlib.md5
|
||||||
@@ -97,6 +98,7 @@ else:
|
|||||||
from http import cookiejar as cookielib
|
from http import cookiejar as cookielib
|
||||||
from xmlrpc.client import ProtocolError
|
from xmlrpc.client import ProtocolError
|
||||||
import html # warning, this is the python3 module and not the web2py html module
|
import html # warning, this is the python3 module and not the web2py html module
|
||||||
|
import ipaddress
|
||||||
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
|
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
|
||||||
iterkeys = lambda d: iter(d.keys())
|
iterkeys = lambda d: iter(d.keys())
|
||||||
itervalues = lambda d: iter(d.values())
|
itervalues = lambda d: iter(d.values())
|
||||||
|
|||||||
+20
-11
@@ -54,7 +54,8 @@ def app_pack(app, request, raise_ex=False, filenames=None):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if filenames is None: app_cleanup(app, request)
|
if filenames is None:
|
||||||
|
app_cleanup(app, request)
|
||||||
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
|
filename = apath('../deposit/web2py.app.%s.w2p' % app, request)
|
||||||
w2p_pack(filename, apath(app, request), filenames=filenames)
|
w2p_pack(filename, apath(app, request), filenames=filenames)
|
||||||
return filename
|
return filename
|
||||||
@@ -104,7 +105,8 @@ def app_cleanup(app, request):
|
|||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': os.unlink(os.path.join(path, f))
|
if f[:1] != '.':
|
||||||
|
os.unlink(os.path.join(path, f))
|
||||||
except IOError:
|
except IOError:
|
||||||
r = False
|
r = False
|
||||||
|
|
||||||
@@ -113,7 +115,8 @@ def app_cleanup(app, request):
|
|||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
if f[:1] != '.':
|
||||||
|
recursive_unlink(os.path.join(path, f))
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
r = False
|
r = False
|
||||||
|
|
||||||
@@ -123,7 +126,8 @@ def app_cleanup(app, request):
|
|||||||
CacheOnDisk(folder=path).clear()
|
CacheOnDisk(folder=path).clear()
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
if f[:1] != '.':
|
||||||
|
recursive_unlink(os.path.join(path, f))
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
r = False
|
r = False
|
||||||
return r
|
return r
|
||||||
@@ -175,10 +179,9 @@ def app_create(app, request, force=False, key=None, info=False):
|
|||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
w2p_unpack('welcome.w2p', path)
|
w2p_unpack('welcome.w2p', path)
|
||||||
for subfolder in [
|
for subfolder in ['models', 'views', 'controllers', 'databases',
|
||||||
'models', 'views', 'controllers', 'databases',
|
'modules', 'cron', 'errors', 'sessions', 'cache',
|
||||||
'modules', 'cron', 'errors', 'sessions', 'cache',
|
'languages', 'static', 'private', 'uploads']:
|
||||||
'languages', 'static', 'private', 'uploads']:
|
|
||||||
subpath = os.path.join(path, subfolder)
|
subpath = os.path.join(path, subfolder)
|
||||||
if not os.path.exists(subpath):
|
if not os.path.exists(subpath):
|
||||||
os.mkdir(subpath)
|
os.mkdir(subpath)
|
||||||
@@ -368,7 +371,7 @@ def unzip(filename, dir, subfolder=''):
|
|||||||
for name in sorted(zf.namelist()):
|
for name in sorted(zf.namelist()):
|
||||||
if not name.startswith(subfolder):
|
if not name.startswith(subfolder):
|
||||||
continue
|
continue
|
||||||
#print name[n:]
|
# print name[n:]
|
||||||
if name.endswith('/'):
|
if name.endswith('/'):
|
||||||
folder = os.path.join(dir, name[n:])
|
folder = os.path.join(dir, name[n:])
|
||||||
if not os.path.exists(folder):
|
if not os.path.exists(folder):
|
||||||
@@ -435,16 +438,22 @@ def add_path_first(path):
|
|||||||
if not global_settings.web2py_runtime_gae:
|
if not global_settings.web2py_runtime_gae:
|
||||||
site.addsitedir(path)
|
site.addsitedir(path)
|
||||||
|
|
||||||
|
|
||||||
def try_mkdir(path):
|
def try_mkdir(path):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
os.mkdir(path)
|
if os.path.islink(path):
|
||||||
|
# path is a broken link, try to mkdir the target of the link instead of the link itself.
|
||||||
|
os.mkdir(os.path.realpath(path))
|
||||||
|
else:
|
||||||
|
os.mkdir(path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.strerror == 'File exists': # In case of race condition.
|
if e.strerror == 'File exists': # In case of race condition.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def create_missing_folders():
|
def create_missing_folders():
|
||||||
if not global_settings.web2py_runtime_gae:
|
if not global_settings.web2py_runtime_gae:
|
||||||
for path in ('applications', 'deposit', 'site-packages', 'logs'):
|
for path in ('applications', 'deposit', 'site-packages', 'logs'):
|
||||||
|
|||||||
+1038
File diff suppressed because it is too large
Load Diff
+44
-31
@@ -41,11 +41,12 @@ import imp
|
|||||||
import logging
|
import logging
|
||||||
import types
|
import types
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
logger = logging.getLogger("web2py")
|
|
||||||
from gluon import rewrite
|
from gluon import rewrite
|
||||||
from gluon.custom_import import custom_import_install
|
from gluon.custom_import import custom_import_install
|
||||||
import py_compile
|
import py_compile
|
||||||
|
|
||||||
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
is_pypy = settings.global_settings.is_pypy
|
is_pypy = settings.global_settings.is_pypy
|
||||||
is_gae = settings.global_settings.web2py_runtime_gae
|
is_gae = settings.global_settings.web2py_runtime_gae
|
||||||
is_jython = settings.global_settings.is_jython
|
is_jython = settings.global_settings.is_jython
|
||||||
@@ -111,7 +112,7 @@ class mybuiltin(object):
|
|||||||
NOTE could simple use a dict and populate it,
|
NOTE could simple use a dict and populate it,
|
||||||
NOTE not sure if this changes things though if monkey patching import.....
|
NOTE not sure if this changes things though if monkey patching import.....
|
||||||
"""
|
"""
|
||||||
#__builtins__
|
# __builtins__
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return getattr(builtin, key)
|
return getattr(builtin, key)
|
||||||
@@ -185,7 +186,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
else:
|
else:
|
||||||
statement = "$.web2py.component('%s','%s');" % (url, target)
|
statement = "$.web2py.component('%s','%s');" % (url, target)
|
||||||
attr['_data-w2p_remote'] = url
|
attr['_data-w2p_remote'] = url
|
||||||
if not target is None:
|
if target is not None:
|
||||||
return DIV(content, **attr)
|
return DIV(content, **attr)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -211,7 +212,8 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
request.env.path_info
|
request.env.path_info
|
||||||
other_request.cid = target
|
other_request.cid = target
|
||||||
other_request.env.http_web2py_component_element = target
|
other_request.env.http_web2py_component_element = target
|
||||||
other_request.restful = types.MethodType(request.restful.__func__, other_request) # A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
other_request.restful = types.MethodType(request.restful.__func__, other_request)
|
||||||
|
# A bit nasty but needed to use LOAD on action decorates with @request.restful()
|
||||||
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
other_response.view = '%s/%s.%s' % (c, f, other_request.extension)
|
||||||
|
|
||||||
other_environment = copy.copy(current.globalenv) # NASTY
|
other_environment = copy.copy(current.globalenv) # NASTY
|
||||||
@@ -405,7 +407,7 @@ def build_environment(request, response, session, store_current=True):
|
|||||||
"""
|
"""
|
||||||
Build the environment dictionary into which web2py files are executed.
|
Build the environment dictionary into which web2py files are executed.
|
||||||
"""
|
"""
|
||||||
#h,v = html,validators
|
# h,v = html,validators
|
||||||
environment = dict(_base_environment_)
|
environment = dict(_base_environment_)
|
||||||
|
|
||||||
if not request.env:
|
if not request.env:
|
||||||
@@ -418,7 +420,7 @@ def build_environment(request, response, session, store_current=True):
|
|||||||
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
|
r'^%s/%s/\w+\.py$' % (request.controller, request.function)
|
||||||
]
|
]
|
||||||
|
|
||||||
t = environment['T'] = translator(os.path.join(request.folder,'languages'),
|
t = environment['T'] = translator(os.path.join(request.folder, 'languages'),
|
||||||
request.env.http_accept_language)
|
request.env.http_accept_language)
|
||||||
c = environment['cache'] = Cache(request)
|
c = environment['cache'] = Cache(request)
|
||||||
|
|
||||||
@@ -506,10 +508,12 @@ def compile_models(folder):
|
|||||||
save_pyc(filename)
|
save_pyc(filename)
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def find_exposed_functions(data):
|
def find_exposed_functions(data):
|
||||||
data = regex_longcomments.sub('',data)
|
data = regex_longcomments.sub('', data)
|
||||||
return regex_expose.findall(data)
|
return regex_expose.findall(data)
|
||||||
|
|
||||||
|
|
||||||
def compile_controllers(folder):
|
def compile_controllers(folder):
|
||||||
"""
|
"""
|
||||||
Compiles all the controllers in the application specified by `folder`
|
Compiles all the controllers in the application specified by `folder`
|
||||||
@@ -524,16 +528,19 @@ def compile_controllers(folder):
|
|||||||
command = data + "\nresponse._vars=response._caller(%s)\n" % \
|
command = data + "\nresponse._vars=response._caller(%s)\n" % \
|
||||||
function
|
function
|
||||||
filename = pjoin(folder, 'compiled',
|
filename = pjoin(folder, 'compiled',
|
||||||
'controllers.%s.%s.py' % (fname[:-3],function))
|
'controllers.%s.%s.py' % (fname[:-3], function))
|
||||||
write_file(filename, command)
|
write_file(filename, command)
|
||||||
save_pyc(filename)
|
save_pyc(filename)
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
def model_cmp(a, b, sep='.'):
|
def model_cmp(a, b, sep='.'):
|
||||||
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
|
return cmp(a.count(sep), b.count(sep)) or cmp(a, b)
|
||||||
|
|
||||||
|
|
||||||
def model_cmp_sep(a, b, sep=os.path.sep):
|
def model_cmp_sep(a, b, sep=os.path.sep):
|
||||||
return model_cmp(a,b,sep)
|
return model_cmp(a, b, sep)
|
||||||
|
|
||||||
|
|
||||||
def run_models_in(environment):
|
def run_models_in(environment):
|
||||||
"""
|
"""
|
||||||
@@ -544,7 +551,7 @@ def run_models_in(environment):
|
|||||||
request = current.request
|
request = current.request
|
||||||
folder = request.folder
|
folder = request.folder
|
||||||
c = request.controller
|
c = request.controller
|
||||||
#f = environment['request'].function
|
# f = environment['request'].function
|
||||||
response = current.response
|
response = current.response
|
||||||
|
|
||||||
path = pjoin(folder, 'models')
|
path = pjoin(folder, 'models')
|
||||||
@@ -557,9 +564,11 @@ def run_models_in(environment):
|
|||||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
|
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep)
|
||||||
else:
|
else:
|
||||||
if compiled:
|
if compiled:
|
||||||
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), key=lambda f: '{0:03d}'.format(f.count('.')) + f)
|
models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0),
|
||||||
|
key=lambda f: '{0:03d}'.format(f.count('.')) + f)
|
||||||
else:
|
else:
|
||||||
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
|
models = sorted(listdir(path, '^\w+\.py$', 0, sort=False),
|
||||||
|
key=lambda f: '{0:03d}'.format(f.count(os.path.sep)) + f)
|
||||||
|
|
||||||
models_to_run = None
|
models_to_run = None
|
||||||
for model in models:
|
for model in models:
|
||||||
@@ -570,10 +579,10 @@ def run_models_in(environment):
|
|||||||
if models_to_run:
|
if models_to_run:
|
||||||
if compiled:
|
if compiled:
|
||||||
n = len(cpath)+8
|
n = len(cpath)+8
|
||||||
fname = model[n:-4].replace('.','/')+'.py'
|
fname = model[n:-4].replace('.', '/')+'.py'
|
||||||
else:
|
else:
|
||||||
n = len(path)+1
|
n = len(path)+1
|
||||||
fname = model[n:].replace(os.path.sep,'/')
|
fname = model[n:].replace(os.path.sep, '/')
|
||||||
if not regex.search(fname) and c != 'appadmin':
|
if not regex.search(fname) and c != 'appadmin':
|
||||||
continue
|
continue
|
||||||
elif compiled:
|
elif compiled:
|
||||||
@@ -583,6 +592,7 @@ def run_models_in(environment):
|
|||||||
ccode = getcfs(model, model, f)
|
ccode = getcfs(model, model, f)
|
||||||
restricted(ccode, environment, layer=model)
|
restricted(ccode, environment, layer=model)
|
||||||
|
|
||||||
|
|
||||||
def run_controller_in(controller, function, environment):
|
def run_controller_in(controller, function, environment):
|
||||||
"""
|
"""
|
||||||
Runs the controller.function() (for the app specified by
|
Runs the controller.function() (for the app specified by
|
||||||
@@ -596,13 +606,13 @@ def run_controller_in(controller, function, environment):
|
|||||||
badc = 'invalid controller (%s/%s)' % (controller, function)
|
badc = 'invalid controller (%s/%s)' % (controller, function)
|
||||||
badf = 'invalid function (%s/%s)' % (controller, function)
|
badf = 'invalid function (%s/%s)' % (controller, function)
|
||||||
if os.path.exists(cpath):
|
if os.path.exists(cpath):
|
||||||
filename = pjoin(cpath, 'controllers.%s.%s.pyc'
|
filename = pjoin(cpath, 'controllers.%s.%s.pyc' % (controller, function))
|
||||||
% (controller, function))
|
try:
|
||||||
if not os.path.exists(filename):
|
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
|
||||||
|
except IOError:
|
||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||||
web2py_error=badf)
|
web2py_error=badf)
|
||||||
ccode = getcfs(filename, filename, lambda: read_pyc(filename))
|
|
||||||
elif function == '_TEST':
|
elif function == '_TEST':
|
||||||
# TESTING: adjust the path to include site packages
|
# TESTING: adjust the path to include site packages
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
@@ -623,24 +633,24 @@ def run_controller_in(controller, function, environment):
|
|||||||
code += TEST_CODE
|
code += TEST_CODE
|
||||||
ccode = compile2(code, filename)
|
ccode = compile2(code, filename)
|
||||||
else:
|
else:
|
||||||
filename = pjoin(folder, 'controllers/%s.py'
|
filename = pjoin(folder, 'controllers/%s.py' % controller)
|
||||||
% controller)
|
try:
|
||||||
if not os.path.exists(filename):
|
code = getcfs(filename, filename, lambda: read_file(filename))
|
||||||
|
except IOError:
|
||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
rewrite.THREAD_LOCAL.routes.error_message % badc,
|
||||||
web2py_error=badc)
|
web2py_error=badc)
|
||||||
code = getcfs(filename, filename, lambda: read_file(filename))
|
|
||||||
exposed = find_exposed_functions(code)
|
exposed = find_exposed_functions(code)
|
||||||
if not function in exposed:
|
if function not in exposed:
|
||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
rewrite.THREAD_LOCAL.routes.error_message % badf,
|
||||||
web2py_error=badf)
|
web2py_error=badf)
|
||||||
code = "%s\nresponse._vars=response._caller(%s)" % (code, function)
|
code = "%s\nresponse._vars=response._caller(%s)" % (code, function)
|
||||||
layer = "%s:%s" % (filename, 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)
|
restricted(ccode, environment, layer=filename)
|
||||||
response = current.response
|
response = environment["response"]
|
||||||
vars = response._vars
|
vars = response._vars
|
||||||
if response.postprocessing:
|
if response.postprocessing:
|
||||||
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
|
vars = reduce(lambda vars, p: p(vars), response.postprocessing, vars)
|
||||||
@@ -678,7 +688,7 @@ def run_view_in(environment):
|
|||||||
layer = 'file stream'
|
layer = 'file stream'
|
||||||
else:
|
else:
|
||||||
filename = pjoin(folder, 'views', view)
|
filename = pjoin(folder, 'views', view)
|
||||||
if os.path.exists(cpath): # compiled views
|
if os.path.exists(cpath): # compiled views
|
||||||
x = view.replace('/', '.')
|
x = view.replace('/', '.')
|
||||||
files = ['views.%s.pyc' % x]
|
files = ['views.%s.pyc' % x]
|
||||||
is_compiled = os.path.exists(pjoin(cpath, files[0]))
|
is_compiled = os.path.exists(pjoin(cpath, files[0]))
|
||||||
@@ -705,16 +715,19 @@ def run_view_in(environment):
|
|||||||
raise HTTP(404,
|
raise HTTP(404,
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
||||||
web2py_error=badv)
|
web2py_error=badv)
|
||||||
layer = filename
|
|
||||||
# Compile the template
|
|
||||||
ccode = parse_template(view,
|
|
||||||
pjoin(folder, 'views'),
|
|
||||||
context=environment)
|
|
||||||
|
|
||||||
|
# if the view is not compiled
|
||||||
|
if not layer:
|
||||||
|
# Compile the template
|
||||||
|
ccode = parse_template(view,
|
||||||
|
pjoin(folder, 'views'),
|
||||||
|
context=environment)
|
||||||
|
layer = filename
|
||||||
restricted(ccode, environment, layer=layer)
|
restricted(ccode, environment, layer=layer)
|
||||||
# parse_template saves everything in response body
|
# parse_template saves everything in response body
|
||||||
return environment['response'].body.getvalue()
|
return environment['response'].body.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def remove_compiled_application(folder):
|
def remove_compiled_application(folder):
|
||||||
"""
|
"""
|
||||||
Deletes the folder `compiled` containing the compiled application.
|
Deletes the folder `compiled` containing the compiled application.
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ CONTENT_TYPE = {
|
|||||||
'.lha': 'application/x-lha',
|
'.lha': 'application/x-lha',
|
||||||
'.lhs': 'text/x-literate-haskell',
|
'.lhs': 'text/x-literate-haskell',
|
||||||
'.lhz': 'application/x-lhz',
|
'.lhz': 'application/x-lhz',
|
||||||
'.load' : 'text/html',
|
'.load': 'text/html',
|
||||||
'.log': 'text/x-log',
|
'.log': 'text/x-log',
|
||||||
'.lrz': 'application/x-lrzip',
|
'.lrz': 'application/x-lrzip',
|
||||||
'.ltx': 'text/x-tex',
|
'.ltx': 'text/x-tex',
|
||||||
@@ -823,7 +823,7 @@ CONTENT_TYPE = {
|
|||||||
'.xsd': 'application/xml',
|
'.xsd': 'application/xml',
|
||||||
'.xsl': 'application/xslt+xml',
|
'.xsl': 'application/xslt+xml',
|
||||||
'.xslfo': 'text/x-xslfo',
|
'.xslfo': 'text/x-xslfo',
|
||||||
'.xslm' : 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
'.xslm': 'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||||
'.xslt': 'application/xslt+xml',
|
'.xslt': 'application/xslt+xml',
|
||||||
'.xspf': 'application/xspf+xml',
|
'.xspf': 'application/xspf+xml',
|
||||||
'.xul': 'application/vnd.mozilla.xul+xml',
|
'.xul': 'application/vnd.mozilla.xul+xml',
|
||||||
@@ -843,7 +843,7 @@ def contenttype(filename, default='text/plain'):
|
|||||||
"""
|
"""
|
||||||
Returns the Content-Type string matching extension of the given filename.
|
Returns the Content-Type string matching extension of the given filename.
|
||||||
"""
|
"""
|
||||||
filename=to_native(filename)
|
filename = to_native(filename)
|
||||||
i = filename.rfind('.')
|
i = filename.rfind('.')
|
||||||
if i >= 0:
|
if i >= 0:
|
||||||
default = CONTENT_TYPE.get(filename[i:].lower(), default)
|
default = CONTENT_TYPE.get(filename[i:].lower(), default)
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ db = get_db()
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from gluon import *
|
from gluon import *
|
||||||
from pydal.adapters import ADAPTERS, PostgreSQLAdapter
|
from pydal.adapters import adapters, PostgrePsyco
|
||||||
from pydal.helpers.classes import UseDatabaseStoredFile
|
from pydal.helpers.classes import DatabaseStoredFile
|
||||||
|
|
||||||
class HerokuPostgresAdapter(UseDatabaseStoredFile,PostgreSQLAdapter):
|
@adapters.register_for('postgres')
|
||||||
drivers = ('psycopg2',)
|
class HerokuPostgresAdapter(DatabaseStoredFile, PostgrePsyco):
|
||||||
uploads_in_blob = True
|
uploads_in_blob = True
|
||||||
|
|
||||||
ADAPTERS['postgres'] = HerokuPostgresAdapter
|
|
||||||
|
|
||||||
def get_db(name = None, pool_size=10):
|
def get_db(name = None, pool_size=10):
|
||||||
if not name:
|
if not name:
|
||||||
names = [n for n in os.environ.keys()
|
names = [n for n in os.environ.keys()
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class RESIZE(object):
|
|||||||
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
|
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
|
||||||
background.paste(
|
background.paste(
|
||||||
img,
|
img,
|
||||||
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
|
((self.nx - img.size[0]) // 2, (self.ny - img.size[1]) // 2))
|
||||||
background.save(s, 'JPEG', quality=self.quality)
|
background.save(s, 'JPEG', quality=self.quality)
|
||||||
else:
|
else:
|
||||||
img.save(s, 'JPEG', quality=self.quality)
|
img.save(s, 'JPEG', quality=self.quality)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -506,9 +506,9 @@ def ldap_auth(server='ldap',
|
|||||||
l = []
|
l = []
|
||||||
for group in ldap_groups_of_the_user:
|
for group in ldap_groups_of_the_user:
|
||||||
if group in group_mapping:
|
if group in group_mapping:
|
||||||
l += group_mapping[group]
|
l.append(group_mapping[group])
|
||||||
ldap_groups_of_the_user = l
|
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
|
# 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:
|
except AttributeError as e:
|
||||||
db_user_id = db.auth_user.insert(email=username, first_name=username)
|
db_user_id = db.auth_user.insert(email=username, first_name=username)
|
||||||
if not db_user_id:
|
if not db_user_id:
|
||||||
logging.error(
|
logger.error(
|
||||||
'There is no username or email for %s!' % username)
|
'There is no username or email for %s!' % username)
|
||||||
raise
|
raise
|
||||||
# if old pydal version, assume this is a relational database which can do joins
|
# 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):
|
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_group_id[group.role] = group.id
|
||||||
db_groups_of_the_user.append(group.role)
|
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
|
auth_membership_changed = False
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -5,12 +5,19 @@
|
|||||||
# license MIT/BSD/GPL
|
# license MIT/BSD/GPL
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import urllib
|
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
|
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
|
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>[^\]]*)\])?)?)',
|
'(' + META + '|' + DISABLED_META + r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',
|
||||||
re.S)
|
re.S)
|
||||||
regex_strong = re.compile(r'\*\*(?P<t>[^\s*]+( +[^\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_em = re.compile(r"''(?P<t>([^\s']| |'(?!'))+)''")
|
||||||
regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
|
regex_num = re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$")
|
||||||
regex_list = re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\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")
|
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*')
|
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):
|
def make_dict(b):
|
||||||
return '{%s}' % regex_quote.sub("'\g<name>':", 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 = {'None': None, 'True': True, 'False': False}
|
||||||
_safe_names.update(env)
|
_safe_names.update(env)
|
||||||
if isinstance(node_or_string, basestring):
|
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):
|
if isinstance(node_or_string, ast.Expression):
|
||||||
node_or_string = node_or_string.body
|
node_or_string = node_or_string.body
|
||||||
|
|
||||||
@@ -599,14 +629,14 @@ def safe_eval(node_or_string, env):
|
|||||||
if node.id in _safe_names:
|
if node.id in _safe_names:
|
||||||
return _safe_names[node.id]
|
return _safe_names[node.id]
|
||||||
elif isinstance(node, ast.BinOp) and \
|
elif isinstance(node, ast.BinOp) and \
|
||||||
isinstance(node.op, (Add, Sub)) and \
|
isinstance(node.op, (ast.Add, ast.Sub)) and \
|
||||||
isinstance(node.right, Num) and \
|
isinstance(node.right, ast.Num) and \
|
||||||
isinstance(node.right.n, complex) 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)):
|
isinstance(node.left.n, (int, long, float)):
|
||||||
left = node.left.n
|
left = node.left.n
|
||||||
right = node.right.n
|
right = node.right.n
|
||||||
if isinstance(node.op, Add):
|
if isinstance(node.op, ast.Add):
|
||||||
return left + right
|
return left + right
|
||||||
else:
|
else:
|
||||||
return left - right
|
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>'
|
'<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")
|
>>> render("----\\nhello world\\n----\\n")
|
||||||
'<blockquote>hello world</blockquote>'
|
'<blockquote><p>hello world</p></blockquote>'
|
||||||
|
|
||||||
>>> render('[[myanchor]]')
|
>>> render('[[myanchor]]')
|
||||||
'<p><span class="anchor" id="markmin_myanchor"></span></p>'
|
'<p><span class="anchor" id="markmin_myanchor"></span></p>'
|
||||||
@@ -946,7 +976,8 @@ def render(text,
|
|||||||
if protolinks == "default":
|
if protolinks == "default":
|
||||||
protolinks = protolinks_simple
|
protolinks = protolinks_simple
|
||||||
pp = '\n' if pretty_print else ''
|
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)):
|
if not (isinstance(text, str)):
|
||||||
text = str(text or '')
|
text = str(text or '')
|
||||||
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
|
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
|
||||||
@@ -994,7 +1025,7 @@ def render(text,
|
|||||||
return LINK
|
return LINK
|
||||||
|
|
||||||
text = regex_link.sub(mark_link, text)
|
text = regex_link.sub(mark_link, text)
|
||||||
text = escape(text)
|
text = local_html_escape(text)
|
||||||
|
|
||||||
if protolinks:
|
if protolinks:
|
||||||
text = regex_proto.sub(lambda m: protolinks(*m.group('p', 'k')), text)
|
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:
|
if pend and mtag == '.': # paragraph in a list:
|
||||||
out.append(etags.pop())
|
out.append(etags.pop())
|
||||||
ltags.pop()
|
ltags.pop()
|
||||||
for i in xrange(lent - lev):
|
for i in range(lent - lev):
|
||||||
out.append('<' + tag + '>' + pp)
|
out.append('<' + tag + '>' + pp)
|
||||||
etags.append('</' + tag + '>' + pp)
|
etags.append('</' + tag + '>' + pp)
|
||||||
lev += 1
|
lev += 1
|
||||||
@@ -1044,7 +1075,7 @@ def render(text,
|
|||||||
elif lent == lev:
|
elif lent == lev:
|
||||||
if tlev[-1] != tag:
|
if tlev[-1] != tag:
|
||||||
# type of list is changed (ul<=>ol):
|
# type of list is changed (ul<=>ol):
|
||||||
for i in xrange(ltags.count(lent)):
|
for i in range(ltags.count(lent)):
|
||||||
ltags.pop()
|
ltags.pop()
|
||||||
out.append(etags.pop())
|
out.append(etags.pop())
|
||||||
tlev[-1] = tag
|
tlev[-1] = tag
|
||||||
@@ -1209,7 +1240,7 @@ def render(text,
|
|||||||
s = '<blockquote%s%s>%s</blockquote>%s' \
|
s = '<blockquote%s%s>%s</blockquote>%s' \
|
||||||
% (t_cls,
|
% (t_cls,
|
||||||
t_id,
|
t_id,
|
||||||
'\n'.join(strings[bq_begin:lineno]), pp)
|
render('\n'.join(strings[bq_begin:lineno])), pp)
|
||||||
mtag = 'q'
|
mtag = 'q'
|
||||||
else:
|
else:
|
||||||
s = '<hr />'
|
s = '<hr />'
|
||||||
@@ -1322,10 +1353,10 @@ def render(text,
|
|||||||
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
|
t, a, k, p, w = m.group('t', 'a', 'k', 'p', 'w')
|
||||||
if not k:
|
if not k:
|
||||||
return m.group(0)
|
return m.group(0)
|
||||||
k = escape(k)
|
k = local_html_escape(k)
|
||||||
t = t or ''
|
t = t or ''
|
||||||
style = 'width:%s' % w if w else ''
|
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 = ''
|
p_begin = p_end = ''
|
||||||
if p == 'center':
|
if p == 'center':
|
||||||
p_begin = '<p style="text-align:center">'
|
p_begin = '<p style="text-align:center">'
|
||||||
@@ -1349,7 +1380,7 @@ def render(text,
|
|||||||
autolinks, protolinks, class_prefix, id_prefix, pretty_print)
|
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>' \
|
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)
|
% 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' \
|
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)
|
% 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:
|
if not k and not t:
|
||||||
return m.group(0)
|
return m.group(0)
|
||||||
t = t or ''
|
t = t or ''
|
||||||
a = escape(a) if a else ''
|
a = local_html_escape(a) if a else ''
|
||||||
if k:
|
if k:
|
||||||
if '#' in k and ':' not in k.split('#')[0]:
|
if '#' in k and ':' not in k.split('#')[0]:
|
||||||
# wikipage, not external url
|
# wikipage, not external url
|
||||||
k = k.replace('#', '#' + id_prefix)
|
k = k.replace('#', '#' + id_prefix)
|
||||||
k = escape(k)
|
k = local_html_escape(k)
|
||||||
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
|
title = ' title="%s"' % a.replace(META, DISABLED_META) if a else ''
|
||||||
target = ' target="_blank"' if p == 'popup' else ''
|
target = ' target="_blank"' if p == 'popup' else ''
|
||||||
t = render(t, {}, {}, 'br', URL, environment, latex, None,
|
t = render(t, {}, {}, 'br', URL, environment, latex, None,
|
||||||
@@ -1373,7 +1404,7 @@ def render(text,
|
|||||||
if t == 'NEWLINE' and not a:
|
if t == 'NEWLINE' and not a:
|
||||||
return '<br />' + pp
|
return '<br />' + pp
|
||||||
return '<span class="anchor" id="%s">%s</span>' % (
|
return '<span class="anchor" id="%s">%s</span>' % (
|
||||||
escape(id_prefix + t),
|
local_html_escape(id_prefix + t),
|
||||||
render(a, {}, {}, 'br', URL,
|
render(a, {}, {}, 'br', URL,
|
||||||
environment, latex, autolinks,
|
environment, latex, autolinks,
|
||||||
protolinks, class_prefix,
|
protolinks, class_prefix,
|
||||||
@@ -1399,7 +1430,7 @@ def render(text,
|
|||||||
def expand_meta(m):
|
def expand_meta(m):
|
||||||
code, b, p, s = segments.pop(0)
|
code, b, p, s = segments.pop(0)
|
||||||
if code is None or m.group() == DISABLED_META:
|
if code is None or m.group() == DISABLED_META:
|
||||||
return escape(s)
|
return local_html_escape(s)
|
||||||
if b in extra:
|
if b in extra:
|
||||||
if code[:1] == '\n':
|
if code[:1] == '\n':
|
||||||
code = code[1:]
|
code = code[1:]
|
||||||
@@ -1411,7 +1442,7 @@ def render(text,
|
|||||||
return str(extra[b](code))
|
return str(extra[b](code))
|
||||||
elif b == 'cite':
|
elif b == 'cite':
|
||||||
return '[' + ','.join('<a href="#%s" class="%s">%s</a>' %
|
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':
|
elif b == 'latex':
|
||||||
return LATEX % urllib_quote(code)
|
return LATEX % urllib_quote(code)
|
||||||
elif b in html_colors:
|
elif b in html_colors:
|
||||||
@@ -1426,12 +1457,12 @@ def render(text,
|
|||||||
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
|
% (fg, bg, render(code, {}, {}, 'br', URL, environment, latex,
|
||||||
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
|
autolinks, protolinks, class_prefix, id_prefix, pretty_print))
|
||||||
cls = ' class="%s%s"' % (class_prefix, b) if b and b != 'id' else ''
|
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')
|
beg = (code[:1] == '\n')
|
||||||
end = [None, -1][code[-1:] == '\n']
|
end = [None, -1][code[-1:] == '\n']
|
||||||
if beg and end:
|
if beg and end:
|
||||||
return '<pre><code%s%s>%s</code></pre>%s' % (cls, id, escape(code[1:-1]), pp)
|
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, escape(code[beg:end]))
|
return '<code%s%s>%s</code>' % (cls, id, local_html_escape(code[beg:end]))
|
||||||
|
|
||||||
text = regex_expand_meta.sub(expand_meta, text)
|
text = regex_expand_meta.sub(expand_meta, text)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ def MemcacheClient(*a, **b):
|
|||||||
|
|
||||||
class MemcacheClientObj(Client):
|
class MemcacheClientObj(Client):
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
pass
|
||||||
|
|
||||||
meta_storage = {}
|
meta_storage = {}
|
||||||
max_time_expire = 24*3600
|
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>
|
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
from StringIO import StringIO # The pure-Python StringIO supports unicode.
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,30 +8,40 @@ Created by: Ross Peoples <ross.peoples@gmail.com>
|
|||||||
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
|
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import cssmin
|
from . import cssmin
|
||||||
import jsmin
|
from . import jsmin
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
hashlib_md5 = hashlib.md5
|
||||||
|
else:
|
||||||
|
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
|
||||||
|
|
||||||
|
def open_py23(filename, mode):
|
||||||
|
if PY2:
|
||||||
|
f = open(filename, mode + 'b')
|
||||||
|
else:
|
||||||
|
f = open(filename, mode, encoding="utf8")
|
||||||
|
return f
|
||||||
|
|
||||||
def read_binary_file(filename):
|
def read_binary_file(filename):
|
||||||
f = open(filename, 'rb')
|
f = open_py23(filename, 'r')
|
||||||
data = f.read()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def write_binary_file(filename, data):
|
def write_binary_file(filename, data):
|
||||||
f = open(filename, 'wb')
|
f = open_py23(filename, 'w')
|
||||||
f.write(data)
|
f.write(data)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def fix_links(css, static_path):
|
def fix_links(css, static_path):
|
||||||
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
|
return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css)
|
||||||
|
|
||||||
|
|
||||||
def minify(files, path_info, folder, optimize_css, optimize_js,
|
def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||||
ignore_concat=[],
|
ignore_concat=[],
|
||||||
ignore_minify=['/jquery.js', '/anytime.js']):
|
ignore_minify=['/jquery.js', '/anytime.js']):
|
||||||
@@ -109,7 +119,7 @@ def minify(files, path_info, folder, optimize_css, optimize_js,
|
|||||||
js.append(contents)
|
js.append(contents)
|
||||||
else:
|
else:
|
||||||
js.append(filename)
|
js.append(filename)
|
||||||
dest_key = hashlib.md5(repr(processed)).hexdigest()
|
dest_key = hashlib_md5(repr(processed)).hexdigest()
|
||||||
if css and concat_css:
|
if css and concat_css:
|
||||||
css = '\n\n'.join(contents for contents in css)
|
css = '\n\n'.join(contents for contents in css)
|
||||||
if not inline_css:
|
if not inline_css:
|
||||||
|
|||||||
@@ -105,14 +105,14 @@ class ServerProxy(object):
|
|||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
"pseudo method that can be called"
|
"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)"
|
"JSON RPC communication (method invocation)"
|
||||||
|
|
||||||
# build data sent to the service
|
# build data sent to the service
|
||||||
request_id = random.randint(0, sys.maxsize)
|
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:
|
if self.version:
|
||||||
data['jsonrpc'] = self.version #mandatory key/value for jsonrpc2 validation else err -32600
|
data['jsonrpc'] = self.version #mandatory key/value for jsonrpc2 validation else err -32600
|
||||||
request = json.dumps(data)
|
request = json.dumps(data)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class WebClient(object):
|
|||||||
self.forms = {}
|
self.forms = {}
|
||||||
self.history = []
|
self.history = []
|
||||||
self.cookies = {}
|
self.cookies = {}
|
||||||
|
self.cookiejar = cookielib.CookieJar()
|
||||||
self.default_headers = default_headers
|
self.default_headers = default_headers
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
self.session_regex = session_regex and re.compile(session_regex)
|
self.session_regex = session_regex and re.compile(session_regex)
|
||||||
@@ -79,9 +80,8 @@ class WebClient(object):
|
|||||||
cookies = cookies or {}
|
cookies = cookies or {}
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
|
|
||||||
cj = cookielib.CookieJar()
|
|
||||||
args = [
|
args = [
|
||||||
urllib2.HTTPCookieProcessor(cj),
|
urllib2.HTTPCookieProcessor(self.cookiejar),
|
||||||
urllib2.HTTPHandler(debuglevel=0)
|
urllib2.HTTPHandler(debuglevel=0)
|
||||||
]
|
]
|
||||||
# if required do basic auth
|
# if required do basic auth
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
|||||||
try:
|
try:
|
||||||
oname = name if not name.startswith('.') else '.'+name
|
oname = name if not name.startswith('.') else '.'+name
|
||||||
return NATIVE_IMPORTER(oname, globals, locals, fromlist, level)
|
return NATIVE_IMPORTER(oname, globals, locals, fromlist, level)
|
||||||
except ImportError:
|
except (ImportError, KeyError):
|
||||||
items = current.request.folder.split(os.path.sep)
|
items = current.request.folder.split(os.path.sep)
|
||||||
if not items[-1]:
|
if not items[-1]:
|
||||||
items = 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]
|
import_tb = sys.exc_info()[2]
|
||||||
try:
|
try:
|
||||||
return NATIVE_IMPORTER(name, globals, locals, fromlist, level)
|
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
|
raise ImportError(e1, import_tb) # there an import error in the module
|
||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
raise # there is an error in the module
|
raise # there is an error in the module
|
||||||
|
|||||||
+8
-8
@@ -14,6 +14,11 @@ from pydal import DAL as DAL
|
|||||||
from pydal import Field
|
from pydal import Field
|
||||||
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
from pydal.objects import Row, Rows, Table, Query, Set, Expression
|
||||||
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
from pydal import SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||||
|
from gluon.serializers import custom_json, xml
|
||||||
|
from gluon.utils import web2py_uuid
|
||||||
|
from gluon import sqlhtml
|
||||||
|
from pydal.drivers import DRIVERS
|
||||||
|
|
||||||
|
|
||||||
def _default_validators(db, field):
|
def _default_validators(db, field):
|
||||||
"""
|
"""
|
||||||
@@ -78,14 +83,10 @@ def _default_validators(db, field):
|
|||||||
if (field.notnull or field.unique) and field_type not in excluded_fields:
|
if (field.notnull or field.unique) and field_type not in excluded_fields:
|
||||||
requires.insert(0, validators.IS_NOT_EMPTY())
|
requires.insert(0, validators.IS_NOT_EMPTY())
|
||||||
elif not field.notnull and not field.unique and requires:
|
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)
|
requires[0] = \
|
||||||
|
validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
|
||||||
return requires
|
return requires
|
||||||
|
|
||||||
from gluon.serializers import custom_json, xml
|
|
||||||
from gluon.utils import web2py_uuid
|
|
||||||
from gluon import sqlhtml
|
|
||||||
|
|
||||||
|
|
||||||
DAL.serializers = {'json': custom_json, 'xml': xml}
|
DAL.serializers = {'json': custom_json, 'xml': xml}
|
||||||
DAL.validators_method = _default_validators
|
DAL.validators_method = _default_validators
|
||||||
DAL.uuid = lambda x: web2py_uuid()
|
DAL.uuid = lambda x: web2py_uuid()
|
||||||
@@ -96,8 +97,7 @@ DAL.representers = {
|
|||||||
DAL.Field = Field
|
DAL.Field = Field
|
||||||
DAL.Table = Table
|
DAL.Table = Table
|
||||||
|
|
||||||
#: add web2py contrib drivers to pyDAL
|
# add web2py contrib drivers to pyDAL
|
||||||
from pydal.drivers import DRIVERS
|
|
||||||
if not DRIVERS.get('pymysql'):
|
if not DRIVERS.get('pymysql'):
|
||||||
try:
|
try:
|
||||||
from .contrib import pymysql
|
from .contrib import pymysql
|
||||||
|
|||||||
+13
-13
@@ -89,7 +89,7 @@ def communicate(command=None):
|
|||||||
|
|
||||||
# New debugger implementation using dbg and a web UI
|
# 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
|
from threading import RLock
|
||||||
|
|
||||||
interact_lock = RLock()
|
interact_lock = RLock()
|
||||||
@@ -109,11 +109,11 @@ def check_interaction(fn):
|
|||||||
return check_fn
|
return check_fn
|
||||||
|
|
||||||
|
|
||||||
class WebDebugger(dbg.Frontend):
|
class WebDebugger(c_dbg.Frontend):
|
||||||
"""Qdb web2py interface"""
|
"""Qdb web2py interface"""
|
||||||
|
|
||||||
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None):
|
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None):
|
||||||
dbg.Frontend.__init__(self, pipe)
|
c_dbg.Frontend.__init__(self, pipe)
|
||||||
self.clear_interaction()
|
self.clear_interaction()
|
||||||
|
|
||||||
def clear_interaction(self):
|
def clear_interaction(self):
|
||||||
@@ -128,7 +128,7 @@ class WebDebugger(dbg.Frontend):
|
|||||||
run_lock.acquire()
|
run_lock.acquire()
|
||||||
try:
|
try:
|
||||||
while self.pipe.poll():
|
while self.pipe.poll():
|
||||||
dbg.Frontend.run(self)
|
c_dbg.Frontend.run(self)
|
||||||
finally:
|
finally:
|
||||||
run_lock.release()
|
run_lock.release()
|
||||||
|
|
||||||
@@ -149,23 +149,23 @@ class WebDebugger(dbg.Frontend):
|
|||||||
|
|
||||||
@check_interaction
|
@check_interaction
|
||||||
def do_continue(self):
|
def do_continue(self):
|
||||||
dbg.Frontend.do_continue(self)
|
c_dbg.Frontend.do_continue(self)
|
||||||
|
|
||||||
@check_interaction
|
@check_interaction
|
||||||
def do_step(self):
|
def do_step(self):
|
||||||
dbg.Frontend.do_step(self)
|
c_dbg.Frontend.do_step(self)
|
||||||
|
|
||||||
@check_interaction
|
@check_interaction
|
||||||
def do_return(self):
|
def do_return(self):
|
||||||
dbg.Frontend.do_return(self)
|
c_dbg.Frontend.do_return(self)
|
||||||
|
|
||||||
@check_interaction
|
@check_interaction
|
||||||
def do_next(self):
|
def do_next(self):
|
||||||
dbg.Frontend.do_next(self)
|
c_dbg.Frontend.do_next(self)
|
||||||
|
|
||||||
@check_interaction
|
@check_interaction
|
||||||
def do_quit(self):
|
def do_quit(self):
|
||||||
dbg.Frontend.do_quit(self)
|
c_dbg.Frontend.do_quit(self)
|
||||||
|
|
||||||
def do_exec(self, statement):
|
def do_exec(self, statement):
|
||||||
interact_lock.acquire()
|
interact_lock.acquire()
|
||||||
@@ -175,18 +175,18 @@ class WebDebugger(dbg.Frontend):
|
|||||||
# avoid spurious interaction notifications:
|
# avoid spurious interaction notifications:
|
||||||
self.set_burst(2)
|
self.set_burst(2)
|
||||||
# execute the statement in the remote debugger:
|
# execute the statement in the remote debugger:
|
||||||
return dbg.Frontend.do_exec(self, statement)
|
return c_dbg.Frontend.do_exec(self, statement)
|
||||||
finally:
|
finally:
|
||||||
interact_lock.release()
|
interact_lock.release()
|
||||||
|
|
||||||
# create the connection between threads:
|
# create the connection between threads:
|
||||||
|
|
||||||
parent_queue, child_queue = Queue.Queue(), Queue.Queue()
|
parent_queue, child_queue = Queue.Queue(), Queue.Queue()
|
||||||
front_conn = dbg.QueuePipe("parent", parent_queue, child_queue)
|
front_conn = c_dbg.QueuePipe("parent", parent_queue, child_queue)
|
||||||
child_conn = dbg.QueuePipe("child", child_queue, parent_queue)
|
child_conn = c_dbg.QueuePipe("child", child_queue, parent_queue)
|
||||||
|
|
||||||
web_debugger = WebDebugger(front_conn) # frontend
|
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
|
dbg = dbg_debugger
|
||||||
|
|
||||||
# enable getting context (stack, globals/locals) at interaction
|
# enable getting context (stack, globals/locals) at interaction
|
||||||
|
|||||||
+14
-15
@@ -9,19 +9,19 @@ Based on http://code.activestate.com/recipes/52257/
|
|||||||
|
|
||||||
Licensed under the PSF License
|
Licensed under the PSF License
|
||||||
"""
|
"""
|
||||||
|
from gluon._compat import to_unicode
|
||||||
import codecs
|
import codecs
|
||||||
|
|
||||||
# None represents a potentially variable byte. "##" in the XML spec...
|
# None represents a potentially variable byte. "##" in the XML spec...
|
||||||
autodetect_dict = { # bytepattern : ("name",
|
autodetect_dict = { # bytepattern : ("name",
|
||||||
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
|
(0x00, 0x00, 0xFE, 0xFF): ("ucs4_be"),
|
||||||
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
|
(0xFF, 0xFE, 0x00, 0x00): ("ucs4_le"),
|
||||||
(0xFE, 0xFF, None, None): ("utf_16_be"),
|
(0xFE, 0xFF, None, None): ("utf_16_be"),
|
||||||
(0xFF, 0xFE, None, None): ("utf_16_le"),
|
(0xFF, 0xFE, None, None): ("utf_16_le"),
|
||||||
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
|
(0x00, 0x3C, 0x00, 0x3F): ("utf_16_be"),
|
||||||
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
|
(0x3C, 0x00, 0x3F, 0x00): ("utf_16_le"),
|
||||||
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
|
(0x3C, 0x3F, 0x78, 0x6D): ("utf_8"),
|
||||||
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
|
(0x4C, 0x6F, 0xA7, 0x94): ("EBCDIC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -36,10 +36,10 @@ def autoDetectXMLEncoding(buffer):
|
|||||||
# buffer at once but otherwise we'd have to decode a character at
|
# buffer at once but otherwise we'd have to decode a character at
|
||||||
# a time looking for the quote character...that's a pain
|
# a time looking for the quote character...that's a pain
|
||||||
|
|
||||||
encoding = "utf_8" # according to the XML spec, this is the default
|
encoding = "utf_8"
|
||||||
# this code successively tries to refine the default
|
# according to the XML spec, this is the default this code successively tries to refine the default
|
||||||
# whenever it fails to refine, it falls back to
|
# whenever it fails to refine, it falls back to the last place encoding was set.
|
||||||
# the last place encoding was set.
|
|
||||||
if len(buffer) >= 4:
|
if len(buffer) >= 4:
|
||||||
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
|
bytes = (byte1, byte2, byte3, byte4) = tuple(map(ord, buffer[0:4]))
|
||||||
enc_info = autodetect_dict.get(bytes, None)
|
enc_info = autodetect_dict.get(bytes, None)
|
||||||
@@ -51,8 +51,7 @@ def autoDetectXMLEncoding(buffer):
|
|||||||
enc_info = None
|
enc_info = None
|
||||||
|
|
||||||
if enc_info:
|
if enc_info:
|
||||||
encoding = enc_info # we've got a guess... these are
|
encoding = enc_info # we've got a guess... these are the new defaults
|
||||||
#the new defaults
|
|
||||||
|
|
||||||
# try to find a more precise encoding using xml declaration
|
# try to find a more precise encoding using xml declaration
|
||||||
secret_decoder_ring = codecs.lookup(encoding)[1]
|
secret_decoder_ring = codecs.lookup(encoding)[1]
|
||||||
@@ -77,4 +76,4 @@ def autoDetectXMLEncoding(buffer):
|
|||||||
|
|
||||||
def decoder(buffer):
|
def decoder(buffer):
|
||||||
encoding = autoDetectXMLEncoding(buffer)
|
encoding = autoDetectXMLEncoding(buffer)
|
||||||
return buffer.decode(encoding).encode('utf8')
|
return to_unicode(buffer, charset=encoding)
|
||||||
|
|||||||
+2
-2
@@ -415,10 +415,10 @@ def fix_newlines(path):
|
|||||||
|\r|
|
|\r|
|
||||||
)''')
|
)''')
|
||||||
for filename in listdir(path, '.*\.(py|html)$', drop=False):
|
for filename in listdir(path, '.*\.(py|html)$', drop=False):
|
||||||
rdata = read_file(filename, 'rb')
|
rdata = read_file(filename, 'r')
|
||||||
wdata = regex.sub('\n', rdata)
|
wdata = regex.sub('\n', rdata)
|
||||||
if wdata != rdata:
|
if wdata != rdata:
|
||||||
write_file(filename, wdata, 'wb')
|
write_file(filename, wdata, 'w')
|
||||||
|
|
||||||
|
|
||||||
def copystream(
|
def copystream(
|
||||||
|
|||||||
+23
-20
@@ -13,7 +13,8 @@ Contains the classes for the global used variables:
|
|||||||
- Session
|
- Session
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, unicodeT, long
|
from gluon._compat import pickle, StringIO, copyreg, Cookie, urlparse, PY2, iteritems, to_unicode, to_native, \
|
||||||
|
unicodeT, long, hashlib_md5
|
||||||
from gluon.storage import Storage, List
|
from gluon.storage import Storage, List
|
||||||
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
|
from gluon.streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE
|
||||||
from gluon.contenttype import contenttype
|
from gluon.contenttype import contenttype
|
||||||
@@ -30,7 +31,7 @@ from gluon.fileutils import copystream
|
|||||||
import hashlib
|
import hashlib
|
||||||
from pydal.contrib import portalocker
|
from pydal.contrib import portalocker
|
||||||
from pickle import Pickler, MARK, DICT, EMPTY_DICT
|
from pickle import Pickler, MARK, DICT, EMPTY_DICT
|
||||||
#from types import DictionaryType
|
# from types import DictionaryType
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
@@ -48,7 +49,7 @@ PAST = 'Sat, 1-Jan-1971 00:00:00'
|
|||||||
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
|
FUTURE = 'Tue, 1-Dec-2999 23:59:59'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#FIXME PY3
|
# FIXME PY3
|
||||||
from gluon.contrib.minify import minify
|
from gluon.contrib.minify import minify
|
||||||
have_minify = True
|
have_minify = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -79,6 +80,7 @@ template_mapping = {
|
|||||||
'js:inline': js_inline
|
'js:inline': js_inline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# IMPORTANT:
|
# IMPORTANT:
|
||||||
# this is required so that pickled dict(s) and class.__dict__
|
# this is required so that pickled dict(s) and class.__dict__
|
||||||
# are sorted and web2py can detect without ambiguity when a session changes
|
# are sorted and web2py can detect without ambiguity when a session changes
|
||||||
@@ -89,9 +91,11 @@ class SortingPickler(Pickler):
|
|||||||
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
|
self._batch_setitems([(key, obj[key]) for key in sorted(obj)])
|
||||||
|
|
||||||
if PY2:
|
if PY2:
|
||||||
#FIXME PY3
|
|
||||||
SortingPickler.dispatch = copy.copy(Pickler.dispatch)
|
SortingPickler.dispatch = copy.copy(Pickler.dispatch)
|
||||||
SortingPickler.dispatch[dict] = SortingPickler.save_dict
|
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):
|
def sorting_dumps(obj, protocol=None):
|
||||||
@@ -119,7 +123,7 @@ def copystream_progress(request, chunk_size=10 ** 5):
|
|||||||
dest = tempfile.NamedTemporaryFile()
|
dest = tempfile.NamedTemporaryFile()
|
||||||
except NotImplementedError: # and GAE this
|
except NotImplementedError: # and GAE this
|
||||||
dest = tempfile.TemporaryFile()
|
dest = tempfile.TemporaryFile()
|
||||||
if not 'X-Progress-ID' in request.get_vars:
|
if 'X-Progress-ID' not in request.get_vars:
|
||||||
copystream(source, dest, size, chunk_size)
|
copystream(source, dest, size, chunk_size)
|
||||||
return dest
|
return dest
|
||||||
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
|
cache_key = 'X-Progress-ID:' + request.get_vars['X-Progress-ID']
|
||||||
@@ -197,7 +201,8 @@ class Request(Storage):
|
|||||||
"""Takes the QUERY_STRING and unpacks it to get_vars
|
"""Takes the QUERY_STRING and unpacks it to get_vars
|
||||||
"""
|
"""
|
||||||
query_string = self.env.get('query_string', '')
|
query_string = self.env.get('query_string', '')
|
||||||
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
|
dget = urlparse.parse_qs(query_string, keep_blank_values=1)
|
||||||
|
# Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
|
||||||
get_vars = self._get_vars = Storage(dget)
|
get_vars = self._get_vars = Storage(dget)
|
||||||
for (key, value) in iteritems(get_vars):
|
for (key, value) in iteritems(get_vars):
|
||||||
if isinstance(value, list) and len(value) == 1:
|
if isinstance(value, list) and len(value) == 1:
|
||||||
@@ -227,8 +232,7 @@ class Request(Storage):
|
|||||||
body.seek(0)
|
body.seek(0)
|
||||||
|
|
||||||
# parse POST variables on POST, PUT, BOTH only in post_vars
|
# parse POST variables on POST, PUT, BOTH only in post_vars
|
||||||
if (body and not is_json
|
if body and not is_json and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH'):
|
||||||
and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
|
|
||||||
query_string = env.pop('QUERY_STRING', None)
|
query_string = env.pop('QUERY_STRING', None)
|
||||||
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
|
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
|
||||||
try:
|
try:
|
||||||
@@ -349,14 +353,14 @@ class Request(Storage):
|
|||||||
current.session.forget()
|
current.session.forget()
|
||||||
redirect(URL(scheme='https', args=self.args, vars=self.vars))
|
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 wrapper(action, request=self):
|
||||||
def f(_action=action, *a, **b):
|
def f(_action=action, *a, **b):
|
||||||
request.is_restful = True
|
request.is_restful = True
|
||||||
env = request.env
|
env = request.env
|
||||||
is_json = env.content_type=='application/json'
|
is_json = env.content_type == 'application/json'
|
||||||
method = env.request_method
|
method = env.request_method
|
||||||
if len(request.args) and '.' in request.args[-1]:
|
if not ignore_extension and len(request.args) and '.' in request.args[-1]:
|
||||||
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
|
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
|
||||||
current.response.headers['Content-Type'] = \
|
current.response.headers['Content-Type'] = \
|
||||||
contenttype('.' + request.extension.lower())
|
contenttype('.' + request.extension.lower())
|
||||||
@@ -415,7 +419,6 @@ class Response(Storage):
|
|||||||
if not escape:
|
if not escape:
|
||||||
self.body.write(str(data))
|
self.body.write(str(data))
|
||||||
else:
|
else:
|
||||||
# FIXME PY3:
|
|
||||||
self.body.write(to_native(xmlescape(data)))
|
self.body.write(to_native(xmlescape(data)))
|
||||||
|
|
||||||
def render(self, *a, **b):
|
def render(self, *a, **b):
|
||||||
@@ -451,13 +454,13 @@ class Response(Storage):
|
|||||||
for meta in iteritems((self.meta or {})):
|
for meta in iteritems((self.meta or {})):
|
||||||
k, v = meta
|
k, v = meta
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key), to_native(xmlescape(v[key]))) for key in v) +' />\n'
|
s += '<meta' + ''.join(' %s="%s"' % (xmlescape(key),
|
||||||
|
to_native(xmlescape(v[key]))) for key in v) + ' />\n'
|
||||||
else:
|
else:
|
||||||
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
|
s += '<meta name="%s" content="%s" />\n' % (k, to_native(xmlescape(v)))
|
||||||
self.write(s, escape=False)
|
self.write(s, escape=False)
|
||||||
|
|
||||||
def include_files(self, extensions=None):
|
def include_files(self, extensions=None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Includes files (usually in the head).
|
Includes files (usually in the head).
|
||||||
Can minify and cache local files
|
Can minify and cache local files
|
||||||
@@ -484,8 +487,7 @@ class Response(Storage):
|
|||||||
|
|
||||||
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
|
if have_minify and ((self.optimize_css and has_css) or (self.optimize_js and has_js)):
|
||||||
# cache for 5 minutes by default
|
# cache for 5 minutes by default
|
||||||
key = hashlib.md5(repr(files)).hexdigest()
|
key = hashlib_md5(repr(files)).hexdigest()
|
||||||
|
|
||||||
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
cache = self.cache_includes or (current.cache.ram, 60 * 5)
|
||||||
|
|
||||||
def call_minify(files=files):
|
def call_minify(files=files):
|
||||||
@@ -523,6 +525,7 @@ class Response(Storage):
|
|||||||
tmpl = template_mapping.get(f)
|
tmpl = template_mapping.get(f)
|
||||||
if tmpl:
|
if tmpl:
|
||||||
s.append(tmpl % item[1])
|
s.append(tmpl % item[1])
|
||||||
|
|
||||||
self.write(''.join(s), escape=False)
|
self.write(''.join(s), escape=False)
|
||||||
|
|
||||||
def stream(self,
|
def stream(self,
|
||||||
@@ -578,9 +581,9 @@ class Response(Storage):
|
|||||||
if hasattr(stream, 'name'):
|
if hasattr(stream, 'name'):
|
||||||
filename = stream.name
|
filename = stream.name
|
||||||
|
|
||||||
if filename and not 'content-type' in keys:
|
if filename and 'content-type' not in keys:
|
||||||
headers['Content-Type'] = contenttype(filename)
|
headers['Content-Type'] = contenttype(filename)
|
||||||
if filename and not 'content-length' in keys:
|
if filename and 'content-length' not in keys:
|
||||||
try:
|
try:
|
||||||
headers['Content-Length'] = \
|
headers['Content-Length'] = \
|
||||||
os.path.getsize(filename)
|
os.path.getsize(filename)
|
||||||
@@ -1022,7 +1025,7 @@ class Session(Storage):
|
|||||||
if self._forget:
|
if self._forget:
|
||||||
del rcookies[response.session_id_name]
|
del rcookies[response.session_id_name]
|
||||||
return
|
return
|
||||||
if self.get('httponly_cookies',True):
|
if self.get('httponly_cookies', True):
|
||||||
scookies['HttpOnly'] = True
|
scookies['HttpOnly'] = True
|
||||||
if self._secure:
|
if self._secure:
|
||||||
scookies['secure'] = True
|
scookies['secure'] = True
|
||||||
@@ -1193,7 +1196,7 @@ class Session(Storage):
|
|||||||
if (not response.session_id or
|
if (not response.session_id or
|
||||||
not response.session_filename or
|
not response.session_filename or
|
||||||
self._forget
|
self._forget
|
||||||
or self._unchanged(response)):
|
or self._unchanged(response)):
|
||||||
# self.clear_session_cookies()
|
# self.clear_session_cookies()
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
+13
-13
@@ -182,8 +182,8 @@ class Highlighter(object):
|
|||||||
)),
|
)),
|
||||||
'PYTHONMultilineString': (python_tokenizer,
|
'PYTHONMultilineString': (python_tokenizer,
|
||||||
(('ENDMULTILINESTRING',
|
(('ENDMULTILINESTRING',
|
||||||
re.compile(r'.*?("""|\'\'\')',
|
re.compile(r'.*?("""|\'\'\')',
|
||||||
re.DOTALL), 'color: darkred'), )),
|
re.DOTALL), 'color: darkred'), )),
|
||||||
'HTML': (html_tokenizer, (
|
'HTML': (html_tokenizer, (
|
||||||
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
|
('GOTOPYTHON', re.compile(r'\{\{'), 'color: red'),
|
||||||
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
|
('COMMENT', re.compile(r'<!--[^>]*-->|<!>'),
|
||||||
@@ -209,7 +209,7 @@ class Highlighter(object):
|
|||||||
mode = self.mode
|
mode = self.mode
|
||||||
while i < len(data):
|
while i < len(data):
|
||||||
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
|
for (token, o_re, style) in Highlighter.all_styles[mode][1]:
|
||||||
if not token in self.suppress_tokens:
|
if token not in self.suppress_tokens:
|
||||||
match = o_re.match(data, i)
|
match = o_re.match(data, i)
|
||||||
if match:
|
if match:
|
||||||
if style:
|
if style:
|
||||||
@@ -221,7 +221,7 @@ class Highlighter(object):
|
|||||||
new_mode = \
|
new_mode = \
|
||||||
Highlighter.all_styles[mode][0](self,
|
Highlighter.all_styles[mode][0](self,
|
||||||
token, match, style)
|
token, match, style)
|
||||||
if not new_mode is None:
|
if new_mode is not None:
|
||||||
mode = new_mode
|
mode = new_mode
|
||||||
i += max(1, len(match.group()))
|
i += max(1, len(match.group()))
|
||||||
break
|
break
|
||||||
@@ -241,9 +241,9 @@ class Highlighter(object):
|
|||||||
style = self.styles[token]
|
style = self.styles[token]
|
||||||
if self.span_style != style:
|
if self.span_style != style:
|
||||||
if style != 'Keep':
|
if style != 'Keep':
|
||||||
if not self.span_style is None:
|
if self.span_style is not None:
|
||||||
self.output.append('</span>')
|
self.output.append('</span>')
|
||||||
if not style is None:
|
if style is not None:
|
||||||
self.output.append('<span style="%s">' % style)
|
self.output.append('<span style="%s">' % style)
|
||||||
self.span_style = style
|
self.span_style = style
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ def highlight(
|
|||||||
):
|
):
|
||||||
styles = styles or {}
|
styles = styles or {}
|
||||||
attributes = attributes or {}
|
attributes = attributes or {}
|
||||||
if not 'CODE' in styles:
|
if 'CODE' not in styles:
|
||||||
code_style = """
|
code_style = """
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-family: Bitstream Vera Sans Mono,monospace;
|
font-family: Bitstream Vera Sans Mono,monospace;
|
||||||
@@ -272,7 +272,7 @@ def highlight(
|
|||||||
white-space: pre !important;\n"""
|
white-space: pre !important;\n"""
|
||||||
else:
|
else:
|
||||||
code_style = styles['CODE']
|
code_style = styles['CODE']
|
||||||
if not 'LINENUMBERS' in styles:
|
if 'LINENUMBERS' not in styles:
|
||||||
linenumbers_style = """
|
linenumbers_style = """
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-family: Bitstream Vera Sans Mono,monospace;
|
font-family: Bitstream Vera Sans Mono,monospace;
|
||||||
@@ -283,7 +283,7 @@ def highlight(
|
|||||||
color: #A0A0A0;\n"""
|
color: #A0A0A0;\n"""
|
||||||
else:
|
else:
|
||||||
linenumbers_style = styles['LINENUMBERS']
|
linenumbers_style = styles['LINENUMBERS']
|
||||||
if not 'LINEHIGHLIGHT' in styles:
|
if 'LINEHIGHLIGHT' not in styles:
|
||||||
linehighlight_style = "background-color: #EBDDE2;"
|
linehighlight_style = "background-color: #EBDDE2;"
|
||||||
else:
|
else:
|
||||||
linehighlight_style = styles['LINEHIGHLIGHT']
|
linehighlight_style = styles['LINEHIGHLIGHT']
|
||||||
@@ -333,8 +333,9 @@ def highlight(
|
|||||||
== '_' and value])
|
== '_' and value])
|
||||||
if fa:
|
if fa:
|
||||||
fa = ' ' + fa
|
fa = ' ' + fa
|
||||||
return '<table%s><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td><td><pre style="%s">%s</pre></td></tr></table>'\
|
return '<table%s><tr style="vertical-align:top;">' \
|
||||||
% (fa, linenumbers_style, numbers, code_style, code)
|
'<td style="min-width:40px; text-align: right;"><pre style="%s">%s</pre></td>' \
|
||||||
|
'<td><pre style="%s">%s</pre></td></tr></table>' % (fa, linenumbers_style, numbers, code_style, code)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -342,5 +343,4 @@ if __name__ == '__main__':
|
|||||||
argfp = open(sys.argv[1])
|
argfp = open(sys.argv[1])
|
||||||
data = argfp.read()
|
data = argfp.read()
|
||||||
argfp.close()
|
argfp.close()
|
||||||
print('<html><body>' + highlight(data, sys.argv[2])\
|
print('<html><body>' + highlight(data, sys.argv[2]) + '</body></html>')
|
||||||
+ '</body></html>')
|
|
||||||
|
|||||||
+29
-40
@@ -20,7 +20,8 @@ import urllib
|
|||||||
import base64
|
import base64
|
||||||
from gluon import sanitizer, decoder
|
from gluon import sanitizer, decoder
|
||||||
import itertools
|
import itertools
|
||||||
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type
|
from gluon._compat import reduce, pickle, copyreg, HTMLParser, name2codepoint, iteritems, unichr, unicodeT, \
|
||||||
|
urllib_quote, to_bytes, to_native, to_unicode, basestring, urlencode, implements_bool, text_type, long
|
||||||
from gluon.utils import local_html_escape
|
from gluon.utils import local_html_escape
|
||||||
import marshal
|
import marshal
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ __all__ = [
|
|||||||
|
|
||||||
DEFAULT_PASSWORD_DISPLAY = '*' * 8
|
DEFAULT_PASSWORD_DISPLAY = '*' * 8
|
||||||
|
|
||||||
|
|
||||||
def xmlescape(data, quote=True):
|
def xmlescape(data, quote=True):
|
||||||
"""
|
"""
|
||||||
Returns an escaped string of the provided data
|
Returns an escaped string of the provided data
|
||||||
@@ -124,10 +126,9 @@ def xmlescape(data, quote=True):
|
|||||||
|
|
||||||
if not(isinstance(data, (text_type, bytes))):
|
if not(isinstance(data, (text_type, bytes))):
|
||||||
# i.e., integers
|
# i.e., integers
|
||||||
data=str(data)
|
data = str(data)
|
||||||
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
|
data = to_bytes(data, 'utf8', 'xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
# ... and do the escaping
|
# ... and do the escaping
|
||||||
data = local_html_escape(data, quote)
|
data = local_html_escape(data, quote)
|
||||||
return data
|
return data
|
||||||
@@ -596,10 +597,10 @@ class XML(XmlComponent):
|
|||||||
for A, IMG and BlockQuote).
|
for A, IMG and BlockQuote).
|
||||||
The key is the tag; the value is a list of allowed attributes.
|
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):
|
if isinstance(text, unicodeT):
|
||||||
text = to_native(text.encode('utf8', 'xmlcharrefreplace'))
|
text = to_native(text.encode('utf8', 'xmlcharrefreplace'))
|
||||||
|
if sanitize:
|
||||||
|
text = sanitizer.sanitize(text, permitted_tags, allowed_attributes)
|
||||||
elif isinstance(text, bytes):
|
elif isinstance(text, bytes):
|
||||||
text = to_native(text)
|
text = to_native(text)
|
||||||
elif not isinstance(text, str):
|
elif not isinstance(text, str):
|
||||||
@@ -671,6 +672,7 @@ def XML_pickle(data):
|
|||||||
return XML_unpickle, (marshal.dumps(str(data)),)
|
return XML_unpickle, (marshal.dumps(str(data)),)
|
||||||
copyreg.pickle(XML, XML_pickle, XML_unpickle)
|
copyreg.pickle(XML, XML_pickle, XML_unpickle)
|
||||||
|
|
||||||
|
|
||||||
@implements_bool
|
@implements_bool
|
||||||
class DIV(XmlComponent):
|
class DIV(XmlComponent):
|
||||||
"""
|
"""
|
||||||
@@ -998,9 +1000,9 @@ class DIV(XmlComponent):
|
|||||||
if isinstance(c, XmlComponent):
|
if isinstance(c, XmlComponent):
|
||||||
s = c.flatten(render)
|
s = c.flatten(render)
|
||||||
elif render:
|
elif render:
|
||||||
s = render(str(c))
|
s = render(to_native(c))
|
||||||
else:
|
else:
|
||||||
s = str(c)
|
s = to_native(c)
|
||||||
text += s
|
text += s
|
||||||
if render:
|
if render:
|
||||||
text = render(text, self.tag, self.attributes)
|
text = render(text, self.tag, self.attributes)
|
||||||
@@ -1281,7 +1283,6 @@ class __TAG__(XmlComponent):
|
|||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name[-1:] == '_':
|
if name[-1:] == '_':
|
||||||
name = name[:-1] + '/'
|
name = name[:-1] + '/'
|
||||||
name=to_bytes(name)
|
|
||||||
return lambda *a, **b: __tag_div__(name, *a, **b)
|
return lambda *a, **b: __tag_div__(name, *a, **b)
|
||||||
|
|
||||||
def __call__(self, html):
|
def __call__(self, html):
|
||||||
@@ -1310,8 +1311,10 @@ class HTML(DIV):
|
|||||||
tag = b'html'
|
tag = b'html'
|
||||||
|
|
||||||
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
|
strict = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
|
||||||
transitional = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
|
transitional = \
|
||||||
frameset = b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
|
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'
|
||||||
|
frameset = \
|
||||||
|
b'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n'
|
||||||
html5 = b'<!DOCTYPE HTML>\n'
|
html5 = b'<!DOCTYPE HTML>\n'
|
||||||
|
|
||||||
def xml(self):
|
def xml(self):
|
||||||
@@ -1862,7 +1865,7 @@ class INPUT(DIV):
|
|||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
msg = "Validation error, field:%s %s" % (name,validator)
|
msg = "Validation error, field:%s %s" % (name, validator)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
if errors is not None:
|
if errors is not None:
|
||||||
self.vars[name] = value
|
self.vars[name] = value
|
||||||
@@ -1912,7 +1915,7 @@ class INPUT(DIV):
|
|||||||
name = self.attributes.get('_name', None)
|
name = self.attributes.get('_name', None)
|
||||||
if name and hasattr(self, 'errors') \
|
if name and hasattr(self, 'errors') \
|
||||||
and self.errors.get(name, None) \
|
and self.errors.get(name, None) \
|
||||||
and self['hideerror'] != True:
|
and self['hideerror'] is not True:
|
||||||
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
|
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
|
||||||
return DIV.xml(self) + DIV(
|
return DIV.xml(self) + DIV(
|
||||||
DIV(
|
DIV(
|
||||||
@@ -1980,7 +1983,6 @@ class OPTGROUP(DIV):
|
|||||||
|
|
||||||
|
|
||||||
class SELECT(INPUT):
|
class SELECT(INPUT):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
@@ -2015,7 +2017,7 @@ class SELECT(INPUT):
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
if not self['_multiple']:
|
if not self['_multiple']:
|
||||||
for c in options: # my patch
|
for c in options: # my patch
|
||||||
if ((value is not None) and (str(c['_value']) == str(value))):
|
if (value is not None) and (str(c['_value']) == str(value)):
|
||||||
c['_selected'] = 'selected'
|
c['_selected'] = 'selected'
|
||||||
else:
|
else:
|
||||||
c['_selected'] = None
|
c['_selected'] = None
|
||||||
@@ -2025,7 +2027,7 @@ class SELECT(INPUT):
|
|||||||
else:
|
else:
|
||||||
values = [str(value)]
|
values = [str(value)]
|
||||||
for c in options: # my patch
|
for c in options: # my patch
|
||||||
if ((value is not None) and (str(c['_value']) in values)):
|
if (value is not None) and (str(c['_value']) in values):
|
||||||
c['_selected'] = 'selected'
|
c['_selected'] = 'selected'
|
||||||
else:
|
else:
|
||||||
c['_selected'] = None
|
c['_selected'] = None
|
||||||
@@ -2376,22 +2378,21 @@ class FORM(DIV):
|
|||||||
|
|
||||||
def as_json(self, sanitize=True):
|
def as_json(self, sanitize=True):
|
||||||
d = self.as_dict(flat=True, sanitize=sanitize)
|
d = self.as_dict(flat=True, sanitize=sanitize)
|
||||||
from serializers import json
|
from gluon.serializers import json
|
||||||
return json(d)
|
return json(d)
|
||||||
|
|
||||||
def as_yaml(self, sanitize=True):
|
def as_yaml(self, sanitize=True):
|
||||||
d = self.as_dict(flat=True, sanitize=sanitize)
|
d = self.as_dict(flat=True, sanitize=sanitize)
|
||||||
from serializers import yaml
|
from gluon.serializers import yaml
|
||||||
return yaml(d)
|
return yaml(d)
|
||||||
|
|
||||||
def as_xml(self, sanitize=True):
|
def as_xml(self, sanitize=True):
|
||||||
d = self.as_dict(flat=True, sanitize=sanitize)
|
d = self.as_dict(flat=True, sanitize=sanitize)
|
||||||
from serializers import xml
|
from gluon.serializers import xml
|
||||||
return xml(d)
|
return xml(d)
|
||||||
|
|
||||||
|
|
||||||
class BEAUTIFY(DIV):
|
class BEAUTIFY(DIV):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Turns any list, dictionary, etc into decent looking html.
|
Turns any list, dictionary, etc into decent looking html.
|
||||||
|
|
||||||
@@ -2548,7 +2549,7 @@ class MENU(DIV):
|
|||||||
li['_class'] = li['_class'] + ' ' + self['li_active']
|
li['_class'] = li['_class'] + ' ' + self['li_active']
|
||||||
else:
|
else:
|
||||||
li['_class'] = self['li_active']
|
li['_class'] = self['li_active']
|
||||||
if len(item) <= 4 or item[4] == True:
|
if len(item) <= 4 or item[4] is True:
|
||||||
ul.append(li)
|
ul.append(li)
|
||||||
return ul
|
return ul
|
||||||
|
|
||||||
@@ -2562,7 +2563,7 @@ class MENU(DIV):
|
|||||||
# ex: ('', False, A('title', _href=URL(...), _title="title"))
|
# ex: ('', False, A('title', _href=URL(...), _title="title"))
|
||||||
# ex: (A('title', _href=URL(...), _title="title"), False, None)
|
# ex: (A('title', _href=URL(...), _title="title"), False, None)
|
||||||
custom_items.append(item)
|
custom_items.append(item)
|
||||||
elif len(item) <= 4 or item[4] == True:
|
elif len(item) <= 4 or item[4] is True:
|
||||||
select.append(OPTION(CAT(prefix, item[0]),
|
select.append(OPTION(CAT(prefix, item[0]),
|
||||||
_value=item[2], _selected=item[1]))
|
_value=item[2], _selected=item[1]))
|
||||||
if len(item) > 3 and len(item[3]):
|
if len(item) > 3 and len(item[3]):
|
||||||
@@ -2655,36 +2656,24 @@ class web2pyHTMLParser(HTMLParser):
|
|||||||
"""
|
"""
|
||||||
obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
|
obj = web2pyHTMLParser(text) parses and html/xml text into web2py helpers.
|
||||||
obj.tree contains the root of the tree, and tree can be manipulated
|
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')):
|
def __init__(self, text, closed=('input', 'link')):
|
||||||
HTMLParser.__init__(self)
|
HTMLParser.__init__(self)
|
||||||
self.tree = self.parent = TAG['']()
|
self.tree = self.parent = TAG['']()
|
||||||
self.closed = closed
|
self.closed = closed
|
||||||
self.tags = [x for x in __all__ if isinstance(eval(x), DIV)]
|
|
||||||
self.last = None
|
self.last = None
|
||||||
self.feed(text)
|
self.feed(text)
|
||||||
|
|
||||||
def handle_starttag(self, tagname, attrs):
|
def handle_starttag(self, tagname, attrs):
|
||||||
if tagname.upper() in self.tags:
|
if tagname in self.closed:
|
||||||
tag = eval(tagname.upper())
|
tagname += '/'
|
||||||
else:
|
tag = TAG[tagname]()
|
||||||
if tagname in self.closed:
|
|
||||||
tagname += '/'
|
|
||||||
tag = TAG[tagname]()
|
|
||||||
for key, value in attrs:
|
for key, value in attrs:
|
||||||
tag['_' + key] = value
|
tag['_' + key] = value
|
||||||
tag.parent = self.parent
|
tag.parent = self.parent
|
||||||
self.parent.append(tag)
|
self.parent.append(tag)
|
||||||
if not tag.tag.endswith(b'/'):
|
if not tag.tag.endswith('/'):
|
||||||
self.parent = tag
|
self.parent = tag
|
||||||
else:
|
else:
|
||||||
self.last = tag.tag[:-1]
|
self.last = tag.tag[:-1]
|
||||||
@@ -2707,7 +2696,6 @@ class web2pyHTMLParser(HTMLParser):
|
|||||||
self.parent.append(entitydefs[name])
|
self.parent.append(entitydefs[name])
|
||||||
|
|
||||||
def handle_endtag(self, tagname):
|
def handle_endtag(self, tagname):
|
||||||
tagname = to_bytes(tagname)
|
|
||||||
# this deals with unbalanced tags
|
# this deals with unbalanced tags
|
||||||
if tagname == self.last:
|
if tagname == self.last:
|
||||||
return
|
return
|
||||||
@@ -2717,7 +2705,8 @@ class web2pyHTMLParser(HTMLParser):
|
|||||||
self.parent = self.parent.parent
|
self.parent = self.parent.parent
|
||||||
except:
|
except:
|
||||||
raise RuntimeError("unable to balance tag %s" % tagname)
|
raise RuntimeError("unable to balance tag %s" % tagname)
|
||||||
if parent_tagname[:len(tagname)] == tagname: break
|
if parent_tagname[:len(tagname)] == tagname:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def markdown_serializer(text, tag=None, attr=None):
|
def markdown_serializer(text, tag=None, attr=None):
|
||||||
|
|||||||
+5
-3
@@ -11,7 +11,7 @@ HTTP statuses helpers
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from gluon._compat import iteritems
|
from gluon._compat import iteritems, unicodeT
|
||||||
|
|
||||||
__all__ = ['HTTP', 'redirect']
|
__all__ = ['HTTP', 'redirect']
|
||||||
|
|
||||||
@@ -116,12 +116,14 @@ class HTTP(Exception):
|
|||||||
for k, v in iteritems(headers):
|
for k, v in iteritems(headers):
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
rheaders += [(k, str(item)) for item in v]
|
rheaders += [(k, str(item)) for item in v]
|
||||||
elif not v is None:
|
elif v is not None:
|
||||||
rheaders.append((k, str(v)))
|
rheaders.append((k, str(v)))
|
||||||
responder(status, rheaders)
|
responder(status, rheaders)
|
||||||
if env.get('request_method', '') == 'HEAD':
|
if env.get('request_method', '') == 'HEAD':
|
||||||
return ['']
|
return ['']
|
||||||
elif isinstance(body, (str, bytes, bytearray)):
|
elif isinstance(body, (str, bytes, bytearray)):
|
||||||
|
if isinstance(body, unicodeT):
|
||||||
|
body = body.encode('utf-8')
|
||||||
return [body]
|
return [body]
|
||||||
elif hasattr(body, '__iter__'):
|
elif hasattr(body, '__iter__'):
|
||||||
return body
|
return body
|
||||||
@@ -148,7 +150,7 @@ class HTTP(Exception):
|
|||||||
web2py_error=self.headers.get('web2py_error'))
|
web2py_error=self.headers.get('web2py_error'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"stringify me"
|
"""stringify me"""
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -78,13 +78,9 @@ alert_dependency = ['hashlib', 'uuid']
|
|||||||
# Now we remove the blacklisted modules if we are using the stated
|
# Now we remove the blacklisted modules if we are using the stated
|
||||||
# python version.
|
# python version.
|
||||||
#
|
#
|
||||||
# List of modules deprecated in Python 2.6 or 2.7 that are in the above set
|
# List of modules deprecated in Python 2.7 that are in the above list
|
||||||
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
|
py27_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] # And ['optparse'] but we need it for now
|
||||||
|
|
||||||
if python_version >= '2.6':
|
|
||||||
base_modules += ['json', 'multiprocessing']
|
|
||||||
base_modules = list(set(base_modules).difference(set(py26_deprecated)))
|
|
||||||
|
|
||||||
if python_version >= '2.7':
|
if python_version >= '2.7':
|
||||||
base_modules += ['argparse', 'json', 'multiprocessing']
|
base_modules += ['argparse', 'json', 'multiprocessing']
|
||||||
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
|
base_modules = list(set(base_modules).difference(set(py27_deprecated)))
|
||||||
|
|||||||
+18
-10
@@ -951,32 +951,40 @@ def findT(path, language=DEFAULT_LANGUAGE):
|
|||||||
Note:
|
Note:
|
||||||
Must be run by the admin app
|
Must be run by the admin app
|
||||||
"""
|
"""
|
||||||
|
from gluon.tools import Auth, Crud
|
||||||
lang_file = pjoin(path, 'languages', language + '.py')
|
lang_file = pjoin(path, 'languages', language + '.py')
|
||||||
sentences = read_dict(lang_file)
|
sentences = read_dict(lang_file)
|
||||||
mp = pjoin(path, 'models')
|
mp = pjoin(path, 'models')
|
||||||
cp = pjoin(path, 'controllers')
|
cp = pjoin(path, 'controllers')
|
||||||
vp = pjoin(path, 'views')
|
vp = pjoin(path, 'views')
|
||||||
mop = pjoin(path, 'modules')
|
mop = pjoin(path, 'modules')
|
||||||
|
def add_message(message):
|
||||||
|
if not message.startswith('#') and not '\n' in message:
|
||||||
|
tokens = message.rsplit('##', 1)
|
||||||
|
else:
|
||||||
|
# this allows markmin syntax in translations
|
||||||
|
tokens = [message]
|
||||||
|
if len(tokens) == 2:
|
||||||
|
message = tokens[0].strip() + '##' + tokens[1].strip()
|
||||||
|
if message and not message in sentences:
|
||||||
|
sentences[message] = message.replace("@markmin\x01", "")
|
||||||
for filename in \
|
for filename in \
|
||||||
listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
|
listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
|
||||||
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
|
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
|
||||||
data = to_native(read_locked(filename))
|
data = to_native(read_locked(filename))
|
||||||
items = regex_translate.findall(data)
|
items = regex_translate.findall(data)
|
||||||
items += ["@markmin\x01%s" %x for x in regex_translate_m.findall(data)]
|
for x in regex_translate_m.findall(data):
|
||||||
|
if x[0:3] in ["'''", '"""']: items.append("%s@markmin\x01%s" %(x[0:3], x[3:]))
|
||||||
|
else: items.append("%s@markmin\x01%s" %(x[0], x[1:]))
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
message = safe_eval(item)
|
message = safe_eval(item)
|
||||||
except:
|
except:
|
||||||
continue # silently ignore inproperly formatted strings
|
continue # silently ignore inproperly formatted strings
|
||||||
if not message.startswith('#') and not '\n' in message:
|
add_message(message)
|
||||||
tokens = message.rsplit('##', 1)
|
gluon_msg = [Auth.default_messages, Crud.default_messages]
|
||||||
else:
|
for item in [x for m in gluon_msg for x in m.values() if x is not None]:
|
||||||
# this allows markmin syntax in translations
|
add_message(item)
|
||||||
tokens = [message]
|
|
||||||
if len(tokens) == 2:
|
|
||||||
message = tokens[0].strip() + '##' + tokens[1].strip()
|
|
||||||
if message and not message in sentences:
|
|
||||||
sentences[message] = message.replace("@markmin\x01", "")
|
|
||||||
if not '!langcode!' in sentences:
|
if not '!langcode!' in sentences:
|
||||||
sentences['!langcode!'] = (
|
sentences['!langcode!'] = (
|
||||||
DEFAULT_LANGUAGE if language in ('default', DEFAULT_LANGUAGE) else language)
|
DEFAULT_LANGUAGE if language in ('default', DEFAULT_LANGUAGE) else language)
|
||||||
|
|||||||
+20
-21
@@ -11,7 +11,8 @@ The gluon wsgi application
|
|||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
|
if False:
|
||||||
|
import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
|
||||||
import gc
|
import gc
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -26,7 +27,7 @@ import random
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
from gluon._compat import Cookie, urllib2
|
from gluon._compat import Cookie, urllib2
|
||||||
#from thread import allocate_lock
|
# from thread import allocate_lock
|
||||||
|
|
||||||
from gluon.fileutils import abspath, write_file
|
from gluon.fileutils import abspath, write_file
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
@@ -67,14 +68,14 @@ import gluon.messageboxhandler
|
|||||||
logging.gluon = gluon
|
logging.gluon = gluon
|
||||||
# so we must restore it! Thanks ozancag
|
# so we must restore it! Thanks ozancag
|
||||||
import locale
|
import locale
|
||||||
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
|
locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
|
||||||
|
|
||||||
exists = os.path.exists
|
exists = os.path.exists
|
||||||
pjoin = os.path.join
|
pjoin = os.path.join
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.config.fileConfig(abspath("logging.conf"))
|
logging.config.fileConfig(abspath("logging.conf"))
|
||||||
except: # fails on GAE or when logfile is missing
|
except: # fails on GAE or when logfile is missing
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
@@ -254,6 +255,7 @@ class LazyWSGI(object):
|
|||||||
return [data]
|
return [data]
|
||||||
for item in middleware_apps:
|
for item in middleware_apps:
|
||||||
app = item(app)
|
app = item(app)
|
||||||
|
|
||||||
def caller(app):
|
def caller(app):
|
||||||
return app(self.environ, self.start_response)
|
return app(self.environ, self.start_response)
|
||||||
return lambda caller=caller, app=app: caller(app)
|
return lambda caller=caller, app=app: caller(app)
|
||||||
@@ -294,9 +296,9 @@ def wsgibase(environ, responder):
|
|||||||
response = Response()
|
response = Response()
|
||||||
session = Session()
|
session = Session()
|
||||||
env = request.env
|
env = request.env
|
||||||
#env.web2py_path = global_settings.applications_parent
|
# env.web2py_path = global_settings.applications_parent
|
||||||
env.web2py_version = web2py_version
|
env.web2py_version = web2py_version
|
||||||
#env.update(global_settings)
|
# env.update(global_settings)
|
||||||
static_file = False
|
static_file = False
|
||||||
http_response = None
|
http_response = None
|
||||||
try:
|
try:
|
||||||
@@ -325,7 +327,6 @@ def wsgibase(environ, responder):
|
|||||||
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
|
'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
|
||||||
response.stream(static_file, request=request)
|
response.stream(static_file, request=request)
|
||||||
|
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
# fill in request items
|
# fill in request items
|
||||||
# ##################################################
|
# ##################################################
|
||||||
@@ -356,17 +357,15 @@ def wsgibase(environ, responder):
|
|||||||
cmd_opts = global_settings.cmd_options
|
cmd_opts = global_settings.cmd_options
|
||||||
|
|
||||||
request.update(
|
request.update(
|
||||||
client = client,
|
client=client,
|
||||||
folder = abspath('applications', app) + os.sep,
|
folder=abspath('applications', app) + os.sep,
|
||||||
ajax = x_req_with == 'xmlhttprequest',
|
ajax=x_req_with == 'xmlhttprequest',
|
||||||
cid = env.http_web2py_component_element,
|
cid=env.http_web2py_component_element,
|
||||||
is_local = (env.remote_addr in local_hosts and
|
is_local=(env.remote_addr in local_hosts and client == env.remote_addr),
|
||||||
client == env.remote_addr),
|
is_shell=False,
|
||||||
is_shell = False,
|
is_scheduler=False,
|
||||||
is_scheduler = False,
|
is_https=env.wsgi_url_scheme in HTTPS_SCHEMES or
|
||||||
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
|
request.env.http_x_forwarded_proto in HTTPS_SCHEMES or env.https == 'on'
|
||||||
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
|
||||||
or env.https == 'on'
|
|
||||||
)
|
)
|
||||||
request.url = environ['PATH_INFO']
|
request.url = environ['PATH_INFO']
|
||||||
|
|
||||||
@@ -390,7 +389,7 @@ def wsgibase(environ, responder):
|
|||||||
% 'invalid request',
|
% 'invalid request',
|
||||||
web2py_error='invalid application')
|
web2py_error='invalid application')
|
||||||
elif not request.is_local and exists(disabled):
|
elif not request.is_local and exists(disabled):
|
||||||
five0three = os.path.join(request.folder,'static','503.html')
|
five0three = os.path.join(request.folder, 'static', '503.html')
|
||||||
if os.path.exists(five0three):
|
if os.path.exists(five0three):
|
||||||
raise HTTP(503, file(five0three, 'r').read())
|
raise HTTP(503, file(five0three, 'r').read())
|
||||||
else:
|
else:
|
||||||
@@ -406,7 +405,7 @@ def wsgibase(environ, responder):
|
|||||||
# get the GET and POST data
|
# get the GET and POST data
|
||||||
# ##################################################
|
# ##################################################
|
||||||
|
|
||||||
#parse_get_post_vars(request, environ)
|
# parse_get_post_vars(request, environ)
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
# expose wsgi hooks for convenience
|
# expose wsgi hooks for convenience
|
||||||
@@ -625,7 +624,7 @@ def appfactory(wsgiapp=wsgibase,
|
|||||||
raise BaseException("Can't create dir %s" % profiler_dir)
|
raise BaseException("Can't create dir %s" % profiler_dir)
|
||||||
filepath = pjoin(profiler_dir, 'wtest')
|
filepath = pjoin(profiler_dir, 'wtest')
|
||||||
try:
|
try:
|
||||||
filehandle = open( filepath, 'w' )
|
filehandle = open(filepath, 'w')
|
||||||
filehandle.close()
|
filehandle.close()
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|||||||
+9
-6
@@ -19,13 +19,14 @@ import sched
|
|||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import platform
|
import platform
|
||||||
import gluon.fileutils
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
try:
|
try:
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
except:
|
except:
|
||||||
import pickle
|
import pickle
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
|
from gluon import fileutils
|
||||||
|
from gluon._compat import to_bytes
|
||||||
from pydal.contrib import portalocker
|
from pydal.contrib import portalocker
|
||||||
|
|
||||||
logger = logging.getLogger("web2py.cron")
|
logger = logging.getLogger("web2py.cron")
|
||||||
@@ -116,7 +117,7 @@ class Token(object):
|
|||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = os.path.join(path, 'cron.master')
|
self.path = os.path.join(path, 'cron.master')
|
||||||
if not os.path.exists(self.path):
|
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.master = None
|
||||||
self.now = time.time()
|
self.now = time.time()
|
||||||
|
|
||||||
@@ -139,7 +140,7 @@ class Token(object):
|
|||||||
if portalocker.LOCK_EX is None:
|
if portalocker.LOCK_EX is None:
|
||||||
logger.warning('WEB2PY CRON: Disabled because no file locking')
|
logger.warning('WEB2PY CRON: Disabled because no file locking')
|
||||||
return None
|
return None
|
||||||
self.master = open(self.path, 'rb+')
|
self.master = fileutils.open_file(self.path, 'rb+')
|
||||||
try:
|
try:
|
||||||
ret = None
|
ret = None
|
||||||
portalocker.lock(self.master, portalocker.LOCK_EX)
|
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
|
Writes into cron.master the time when cron job was completed
|
||||||
"""
|
"""
|
||||||
|
ret = self.master.closed
|
||||||
if not self.master.closed:
|
if not self.master.closed:
|
||||||
portalocker.lock(self.master, portalocker.LOCK_EX)
|
portalocker.lock(self.master, portalocker.LOCK_EX)
|
||||||
logger.debug('WEB2PY CRON: Releasing cron lock')
|
logger.debug('WEB2PY CRON: Releasing cron lock')
|
||||||
@@ -177,6 +179,7 @@ class Token(object):
|
|||||||
pickle.dump((self.now, time.time()), self.master)
|
pickle.dump((self.now, time.time()), self.master)
|
||||||
portalocker.unlock(self.master)
|
portalocker.unlock(self.master)
|
||||||
self.master.close()
|
self.master.close()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def rangetolist(s, period='min'):
|
def rangetolist(s, period='min'):
|
||||||
@@ -222,8 +225,8 @@ def parsecronline(line):
|
|||||||
params = line.strip().split(None, 6)
|
params = line.strip().split(None, 6)
|
||||||
if len(params) < 7:
|
if len(params) < 7:
|
||||||
return None
|
return None
|
||||||
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3, 'thu': 4,
|
daysofweek = {'sun': 0, 'mon': 1, 'tue': 2, 'wed': 3,
|
||||||
'fri': 5, 'sat': 6}
|
'thu': 4, 'fri': 5, 'sat': 6}
|
||||||
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
|
for (s, id) in zip(params[:5], ['min', 'hr', 'dom', 'mon', 'dow']):
|
||||||
if not s in [None, '*']:
|
if not s in [None, '*']:
|
||||||
task[id] = []
|
task[id] = []
|
||||||
@@ -236,7 +239,7 @@ def parsecronline(line):
|
|||||||
elif val.isdigit() or val == '-1':
|
elif val.isdigit() or val == '-1':
|
||||||
task[id].append(int(val))
|
task[id].append(int(val))
|
||||||
elif id == 'dow' and val[:3].lower() in daysofweek:
|
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['user'] = params[5]
|
||||||
task['cmd'] = params[6]
|
task['cmd'] = params[6]
|
||||||
return task
|
return task
|
||||||
|
|||||||
+1
-1
Submodule gluon/packages/dal updated: 12bc6d9740...3e0fd7c01c
+4
-1
@@ -137,7 +137,10 @@ class RestrictedError(Exception):
|
|||||||
self.environment = environment
|
self.environment = environment
|
||||||
if layer:
|
if layer:
|
||||||
try:
|
try:
|
||||||
self.traceback = traceback.format_exc()
|
try:
|
||||||
|
self.traceback = traceback.format_exc()
|
||||||
|
except:
|
||||||
|
self.traceback = traceback.format_exc(limit=1)
|
||||||
except:
|
except:
|
||||||
self.traceback = 'no traceback because template parsing error'
|
self.traceback = 'no traceback because template parsing error'
|
||||||
try:
|
try:
|
||||||
|
|||||||
+3
-4
@@ -206,8 +206,7 @@ def url_out(request, environ, application, controller, function,
|
|||||||
if host is True or (host is None and (scheme or port is not None)):
|
if host is True or (host is None and (scheme or port is not None)):
|
||||||
host = request.env.http_host
|
host = request.env.http_host
|
||||||
if not scheme or scheme is True:
|
if not scheme or scheme is True:
|
||||||
scheme = request.env.get('wsgi_url_scheme', 'http').lower() \
|
scheme = request.env.get('wsgi_url_scheme', 'http').lower() if request else 'http'
|
||||||
if request else 'http'
|
|
||||||
if host:
|
if host:
|
||||||
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
|
host_port = host if not port else host.split(':', 1)[0] + ':%s' % port
|
||||||
url = '%s://%s%s' % (scheme, host_port, url)
|
url = '%s://%s%s' % (scheme, host_port, url)
|
||||||
@@ -317,7 +316,7 @@ def load(routes='routes.py', app=None, data=None, rdict=None):
|
|||||||
|
|
||||||
symbols = dict(app=app)
|
symbols = dict(app=app)
|
||||||
try:
|
try:
|
||||||
exec(data + '\n', symbols)
|
exec(data, symbols)
|
||||||
except SyntaxError as e:
|
except SyntaxError as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
'%s has a syntax error and will not be loaded\n' % path
|
'%s has a syntax error and will not be loaded\n' % path
|
||||||
@@ -1040,7 +1039,7 @@ class MapUrlIn(object):
|
|||||||
else:
|
else:
|
||||||
default_function = self.router.default_function # str or None
|
default_function = self.router.default_function # str or None
|
||||||
default_function = self.domain_function or default_function
|
default_function = self.domain_function or default_function
|
||||||
if not arg0 or functions and arg0 not in functions:
|
if not arg0 or functions and arg0.split('.')[0] not in functions:
|
||||||
self.function = default_function or ""
|
self.function = default_function or ""
|
||||||
self.pop_arg_if(arg0 and self.function == arg0)
|
self.pop_arg_if(arg0 and self.function == arg0)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+4
-5
@@ -307,13 +307,13 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
has_futures = False
|
has_futures = False
|
||||||
|
|
||||||
class Future:
|
class Future(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ThreadPoolExecutor:
|
class ThreadPoolExecutor(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class _WorkItem:
|
class _WorkItem(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -784,8 +784,7 @@ class Rocket(object):
|
|||||||
the application developer. Please update your \
|
the application developer. Please update your \
|
||||||
applications to no longer call rocket.stop(True)"
|
applications to no longer call rocket.stop(True)"
|
||||||
try:
|
try:
|
||||||
import warnings
|
raise DeprecationWarning(msg)
|
||||||
raise warnings.DeprecationWarning(msg)
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -11,7 +11,7 @@ Cross-site scripting (XSS) defense
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from gluon._compat import HTMLParser, urlparse, entitydefs, basestring
|
from gluon._compat import HTMLParser, urlparse, entitydefs, basestring
|
||||||
from cgi import escape
|
from gluon.utils import local_html_escape
|
||||||
from formatter import AbstractFormatter
|
from formatter import AbstractFormatter
|
||||||
from xml.sax.saxutils import quoteattr
|
from xml.sax.saxutils import quoteattr
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ __all__ = ['sanitize']
|
|||||||
def xssescape(text):
|
def xssescape(text):
|
||||||
"""Gets rid of < and > and & and, for good measure, :"""
|
"""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):
|
class XssCleaner(HTMLParser):
|
||||||
|
|||||||
+2
-2
@@ -1158,7 +1158,7 @@ class Scheduler(MetaScheduler):
|
|||||||
if not self.db_thread:
|
if not self.db_thread:
|
||||||
logger.debug('thread building own DAL object')
|
logger.debug('thread building own DAL object')
|
||||||
self.db_thread = DAL(
|
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)
|
self.define_tables(self.db_thread, migrate=False)
|
||||||
try:
|
try:
|
||||||
db = self.db_thread
|
db = self.db_thread
|
||||||
@@ -1698,7 +1698,7 @@ def main():
|
|||||||
print('groups for this worker: ' + ', '.join(group_names))
|
print('groups for this worker: ' + ', '.join(group_names))
|
||||||
print('connecting to database in folder: ' + options.db_folder or './')
|
print('connecting to database in folder: ' + options.db_folder or './')
|
||||||
print('using URI: ' + options.db_uri)
|
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...')
|
print('instantiating scheduler...')
|
||||||
scheduler = Scheduler(db=db,
|
scheduler = Scheduler(db=db,
|
||||||
worker_name=options.worker_name,
|
worker_name=options.worker_name,
|
||||||
|
|||||||
+39
-31
@@ -29,7 +29,7 @@ from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
|
|||||||
from pydal.base import DEFAULT
|
from pydal.base import DEFAULT
|
||||||
from pydal.objects import Table, Row, Expression, Field, Set, Rows
|
from pydal.objects import Table, Row, Expression, Field, Set, Rows
|
||||||
from pydal.adapters.base import CALLABLETYPES
|
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 pydal.helpers.classes import Reference, SQLCustomType
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
from gluon.utils import md5_hash
|
from gluon.utils import md5_hash
|
||||||
@@ -405,7 +405,7 @@ class RadioWidget(OptionsWidget):
|
|||||||
cols = attributes.get('cols', 1)
|
cols = attributes.get('cols', 1)
|
||||||
totals = len(options)
|
totals = len(options)
|
||||||
mods = totals % cols
|
mods = totals % cols
|
||||||
rows = totals / cols
|
rows = totals // cols
|
||||||
if mods:
|
if mods:
|
||||||
rows += 1
|
rows += 1
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ class CheckboxesWidget(OptionsWidget):
|
|||||||
cols = attributes.get('cols', 1)
|
cols = attributes.get('cols', 1)
|
||||||
totals = len(options)
|
totals = len(options)
|
||||||
mods = totals % cols
|
mods = totals % cols
|
||||||
rows = totals / cols
|
rows = totals // cols
|
||||||
if mods:
|
if mods:
|
||||||
rows += 1
|
rows += 1
|
||||||
|
|
||||||
@@ -684,9 +684,10 @@ class AutocompleteWidget(object):
|
|||||||
urlvars = request.vars
|
urlvars = request.vars
|
||||||
urlvars[default_var] = 1
|
urlvars[default_var] = 1
|
||||||
self.url = URL(args=request.args, vars=urlvars)
|
self.url = URL(args=request.args, vars=urlvars)
|
||||||
self.callback()
|
self.run_callback = True
|
||||||
else:
|
else:
|
||||||
self.url = request
|
self.url = request
|
||||||
|
self.run_callback = False
|
||||||
|
|
||||||
def callback(self):
|
def callback(self):
|
||||||
if self.keyword in self.request.vars:
|
if self.keyword in self.request.vars:
|
||||||
@@ -759,6 +760,8 @@ class AutocompleteWidget(object):
|
|||||||
raise HTTP(200, '')
|
raise HTTP(200, '')
|
||||||
|
|
||||||
def __call__(self, field, value, **attributes):
|
def __call__(self, field, value, **attributes):
|
||||||
|
if self.run_callback:
|
||||||
|
self.callback()
|
||||||
default = dict(
|
default = dict(
|
||||||
_type='text',
|
_type='text',
|
||||||
value=(value is not None and str(value)) or '',
|
value=(value is not None and str(value)) or '',
|
||||||
@@ -1916,7 +1919,7 @@ class SQLFORM(FORM):
|
|||||||
if 'table_name' in attributes:
|
if 'table_name' in attributes:
|
||||||
del attributes['table_name']
|
del attributes['table_name']
|
||||||
|
|
||||||
return SQLFORM(DAL(None).define_table(table_name, *fields),
|
return SQLFORM(DAL(None).define_table(table_name, *[field.clone() for field in fields]),
|
||||||
**attributes)
|
**attributes)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2153,7 +2156,8 @@ class SQLFORM(FORM):
|
|||||||
ignore_common_filters=None,
|
ignore_common_filters=None,
|
||||||
auto_pagination=True,
|
auto_pagination=True,
|
||||||
use_cursor=False,
|
use_cursor=False,
|
||||||
represent_none=None):
|
represent_none=None,
|
||||||
|
showblobs=False):
|
||||||
|
|
||||||
formstyle = formstyle or current.response.formstyle
|
formstyle = formstyle or current.response.formstyle
|
||||||
if isinstance(query, Set):
|
if isinstance(query, Set):
|
||||||
@@ -2194,7 +2198,7 @@ class SQLFORM(FORM):
|
|||||||
buttondelete='icon trash icon-trash glyphicon glyphicon-trash',
|
buttondelete='icon trash icon-trash glyphicon glyphicon-trash',
|
||||||
buttonedit='icon pen icon-pencil glyphicon glyphicon-pencil',
|
buttonedit='icon pen icon-pencil glyphicon glyphicon-pencil',
|
||||||
buttontable='icon rightarrow icon-arrow-right glyphicon glyphicon-arrow-right',
|
buttontable='icon rightarrow icon-arrow-right glyphicon glyphicon-arrow-right',
|
||||||
buttonview='icon magnifier icon-zoom-in glyphicon glyphicon-zoom-in',
|
buttonview='icon magnifier icon-zoom-in glyphicon glyphicon-zoom-in'
|
||||||
)
|
)
|
||||||
elif not isinstance(ui, dict):
|
elif not isinstance(ui, dict):
|
||||||
raise RuntimeError('SQLFORM.grid ui argument must be a dictionary')
|
raise RuntimeError('SQLFORM.grid ui argument must be a dictionary')
|
||||||
@@ -2328,7 +2332,7 @@ class SQLFORM(FORM):
|
|||||||
if not isinstance(left, (list, tuple)):
|
if not isinstance(left, (list, tuple)):
|
||||||
left = [left]
|
left = [left]
|
||||||
for join in 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]
|
tables = [db[tablename] for tablename in tablenames]
|
||||||
if fields:
|
if fields:
|
||||||
# add missing tablename to virtual fields
|
# add missing tablename to virtual fields
|
||||||
@@ -2340,7 +2344,7 @@ class SQLFORM(FORM):
|
|||||||
else:
|
else:
|
||||||
fields = []
|
fields = []
|
||||||
columns = []
|
columns = []
|
||||||
filter1 = lambda f: isinstance(f, Field) and f.type != 'blob'
|
filter1 = lambda f: isinstance(f, Field) and (f.type!='blob' or showblobs)
|
||||||
filter2 = lambda f: isinstance(f, Field) and f.readable
|
filter2 = lambda f: isinstance(f, Field) and f.readable
|
||||||
for table in tables:
|
for table in tables:
|
||||||
fields += filter(filter1, table)
|
fields += filter(filter1, table)
|
||||||
@@ -2553,6 +2557,7 @@ class SQLFORM(FORM):
|
|||||||
if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
|
if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
|
||||||
expcolumns.append(str(field))
|
expcolumns.append(str(field))
|
||||||
|
|
||||||
|
expcolumns = ['"%s"' % '"."'.join(f.split('.')) for f in expcolumns]
|
||||||
if export_type in exportManager and exportManager[export_type]:
|
if export_type in exportManager and exportManager[export_type]:
|
||||||
if keywords:
|
if keywords:
|
||||||
try:
|
try:
|
||||||
@@ -2862,7 +2867,7 @@ class SQLFORM(FORM):
|
|||||||
for field in columns:
|
for field in columns:
|
||||||
if not field.readable:
|
if not field.readable:
|
||||||
continue
|
continue
|
||||||
if field.type == 'blob':
|
elif field.type == 'blob' and not showblobs:
|
||||||
continue
|
continue
|
||||||
if isinstance(field, Field.Virtual) and field.tablename in row:
|
if isinstance(field, Field.Virtual) and field.tablename in row:
|
||||||
try:
|
try:
|
||||||
@@ -3150,7 +3155,9 @@ class SQLFORM(FORM):
|
|||||||
# if isinstance(linked_tables, dict):
|
# if isinstance(linked_tables, dict):
|
||||||
# linked_tables = linked_tables.get(table._tablename, [])
|
# linked_tables = linked_tables.get(table._tablename, [])
|
||||||
if linked_tables is None or referee in linked_tables:
|
if linked_tables is None or referee in linked_tables:
|
||||||
field.represent = lambda id, r=None, referee=referee, rep=field.represent: 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):
|
except (KeyError, ValueError, TypeError):
|
||||||
redirect(URL(args=table._tablename))
|
redirect(URL(args=table._tablename))
|
||||||
if nargs == len(args) + 1:
|
if nargs == len(args) + 1:
|
||||||
@@ -3311,23 +3318,28 @@ class SQLTABLE(TABLE):
|
|||||||
if not sqlrows:
|
if not sqlrows:
|
||||||
return
|
return
|
||||||
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
|
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
|
||||||
|
fieldmap = dict(zip(sqlrows.colnames, sqlrows.fields))
|
||||||
|
tablemap = dict(((f.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:
|
if not columns:
|
||||||
columns = list(sqlrows.colnames)
|
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 = {}
|
headers = {}
|
||||||
for c in columns:
|
for c in columns:
|
||||||
tfmatch = REGEX_TABLE_DOT_FIELD.match(c)
|
f = fieldmap.get(c)
|
||||||
if tfmatch:
|
if isinstance(f, field_types):
|
||||||
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups()
|
headers[c] = make_name(f)
|
||||||
headers[t + '.' + f] = f.replace('_', ' ').title()
|
|
||||||
else:
|
else:
|
||||||
headers[c] = REGEX_ALIAS_MATCH.sub(r'\2', c)
|
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:
|
if colgroup:
|
||||||
cols = [COL(_id=c.replace('.', '-'), data={'column': i + 1})
|
cols = [COL(_id=c.replace('.', '-'), data={'column': i + 1})
|
||||||
for i, c in enumerate(columns)]
|
for i, c in enumerate(columns)]
|
||||||
@@ -3380,9 +3392,8 @@ class SQLTABLE(TABLE):
|
|||||||
_class += ' rowselected'
|
_class += ' rowselected'
|
||||||
|
|
||||||
for colname in columns:
|
for colname in columns:
|
||||||
matched_column_field = \
|
field = fieldmap.get(colname)
|
||||||
sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD.match(colname)
|
if not isinstance(field, field_types):
|
||||||
if not matched_column_field:
|
|
||||||
if "_extra" in record and colname in record._extra:
|
if "_extra" in record and colname in record._extra:
|
||||||
r = record._extra[colname]
|
r = record._extra[colname]
|
||||||
row.append(TD(r))
|
row.append(TD(r))
|
||||||
@@ -3390,12 +3401,9 @@ class SQLTABLE(TABLE):
|
|||||||
else:
|
else:
|
||||||
raise KeyError(
|
raise KeyError(
|
||||||
"Column %s not found (SQLTABLE)" % colname)
|
"Column %s not found (SQLTABLE)" % colname)
|
||||||
(tablename, fieldname) = matched_column_field.groups()
|
# Virtual fields don't have parent table name...
|
||||||
colname = tablename + '.' + fieldname
|
tablename = colname.split('.', 1)[0]
|
||||||
try:
|
fieldname = field.name
|
||||||
field = sqlrows.db[tablename][fieldname]
|
|
||||||
except (KeyError, AttributeError):
|
|
||||||
field = None
|
|
||||||
if tablename in record \
|
if tablename in record \
|
||||||
and isinstance(record, Row) \
|
and isinstance(record, Row) \
|
||||||
and isinstance(record[tablename], Row):
|
and isinstance(record[tablename], Row):
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .test_contribs import *
|
|||||||
from .test_routes import *
|
from .test_routes import *
|
||||||
from .test_router import *
|
from .test_router import *
|
||||||
from .test_validators import *
|
from .test_validators import *
|
||||||
|
from .test_authapi import *
|
||||||
from .test_tools import *
|
from .test_tools import *
|
||||||
from .test_utils import *
|
from .test_utils import *
|
||||||
from .test_serializers import *
|
from .test_serializers import *
|
||||||
@@ -22,7 +23,8 @@ from .test_appadmin import *
|
|||||||
from .test_web import *
|
from .test_web import *
|
||||||
from .test_sqlhtml import *
|
from .test_sqlhtml import *
|
||||||
from .test_scheduler import *
|
from .test_scheduler import *
|
||||||
|
from .test_cron import *
|
||||||
|
from .test_is_url import *
|
||||||
|
|
||||||
if sys.version[:3] == '2.7':
|
if sys.version[:3] == '2.7':
|
||||||
from .test_is_url import *
|
|
||||||
from .test_old_doctests import *
|
from .test_old_doctests import *
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from gluon import fileutils
|
|||||||
from gluon.dal import DAL, Field, Table
|
from gluon.dal import DAL, Field, Table
|
||||||
from gluon.http import HTTP
|
from gluon.http import HTTP
|
||||||
from gluon.fileutils import open_file
|
from gluon.fileutils import open_file
|
||||||
|
from gluon.cache import CacheInRam
|
||||||
|
|
||||||
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||||
|
|
||||||
@@ -91,7 +92,8 @@ class TestAppAdmin(unittest.TestCase):
|
|||||||
self.run_view()
|
self.run_view()
|
||||||
self.run_view_file_stream()
|
self.run_view_file_stream()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e.message)
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
self.fail('Could not make the view')
|
self.fail('Could not make the view')
|
||||||
|
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
@@ -103,6 +105,21 @@ class TestAppAdmin(unittest.TestCase):
|
|||||||
self._test_index()
|
self._test_index()
|
||||||
remove_compiled_application(appname_path)
|
remove_compiled_application(appname_path)
|
||||||
|
|
||||||
|
def test_index_minify(self):
|
||||||
|
# test for gluon/contrib/minify
|
||||||
|
self.env['response'].optimize_css = 'concat|minify'
|
||||||
|
self.env['response'].optimize_js = 'concat|minify'
|
||||||
|
self.env['current'].cache = Storage({'ram':CacheInRam()})
|
||||||
|
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
|
||||||
|
self._test_index()
|
||||||
|
file_l = os.listdir(os.path.join(appname_path, 'static', 'temp'))
|
||||||
|
file_l.sort()
|
||||||
|
self.assertTrue(len(file_l) == 2)
|
||||||
|
self.assertEqual(file_l[0][0:10], 'compressed')
|
||||||
|
self.assertEqual(file_l[1][0:10], 'compressed')
|
||||||
|
self.assertEqual(file_l[0][-3:], 'css')
|
||||||
|
self.assertEqual(file_l[1][-2:], 'js')
|
||||||
|
|
||||||
def test_select(self):
|
def test_select(self):
|
||||||
request = self.env['request']
|
request = self.env['request']
|
||||||
request.args = List(['db'])
|
request.args = List(['db'])
|
||||||
@@ -115,7 +132,8 @@ class TestAppAdmin(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
self.run_view()
|
self.run_view()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e.message)
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
self.fail('Could not make the view')
|
self.fail('Could not make the view')
|
||||||
|
|
||||||
def test_insert(self):
|
def test_insert(self):
|
||||||
@@ -129,7 +147,8 @@ class TestAppAdmin(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
self.run_view()
|
self.run_view()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e.message)
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
self.fail('Could not make the view')
|
self.fail('Could not make the view')
|
||||||
|
|
||||||
def test_insert_submit(self):
|
def test_insert_submit(self):
|
||||||
@@ -151,7 +170,8 @@ class TestAppAdmin(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
self.run_view()
|
self.run_view()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e.message)
|
import traceback
|
||||||
|
print(traceback.format_exc())
|
||||||
self.fail('Could not make the view')
|
self.fail('Could not make the view')
|
||||||
db = self.env['db']
|
db = self.env['db']
|
||||||
lisa_record = db(db.auth_user.username == 'lisasimpson').select().first()
|
lisa_record = db(db.auth_user.username == 'lisasimpson').select().first()
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#!/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'])
|
||||||
|
|
||||||
|
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')
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Unit tests for cron """
|
||||||
|
import unittest, os
|
||||||
|
from gluon.newcron import Token, crondance
|
||||||
|
|
||||||
|
class TestCron(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
|
def test_Token(self):
|
||||||
|
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
|
||||||
|
t = Token(path=appname_path)
|
||||||
|
self.assertNotEqual(t.acquire(), None)
|
||||||
|
self.assertFalse(t.release())
|
||||||
|
self.assertEqual(t.acquire(), None)
|
||||||
|
self.assertTrue(t.release())
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_crondance(self):
|
||||||
|
#TODO update crondance to return something
|
||||||
|
crondance(os.getcwd())
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from gluon.fileutils import parse_version
|
from gluon.fileutils import parse_version, fix_newlines
|
||||||
|
|
||||||
|
|
||||||
class TestFileUtils(unittest.TestCase):
|
class TestFileUtils(unittest.TestCase):
|
||||||
@@ -22,3 +23,6 @@ class TestFileUtils(unittest.TestCase):
|
|||||||
# Semantic Beta
|
# Semantic Beta
|
||||||
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26')
|
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26')
|
||||||
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
|
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
|
||||||
|
|
||||||
|
def test_fix_newlines(self):
|
||||||
|
fix_newlines(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
+72
-24
@@ -10,27 +10,76 @@ import re
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from gluon.globals import Request, Response, Session
|
from gluon.globals import Request, Response, Session
|
||||||
|
from gluon.rewrite import regex_url_in
|
||||||
from gluon import URL
|
from gluon import URL
|
||||||
from gluon._compat import basestring
|
from gluon._compat import basestring
|
||||||
|
|
||||||
|
|
||||||
def setup_clean_session():
|
def setup_clean_session():
|
||||||
request = Request(env={})
|
request = Request(env={})
|
||||||
request.application = 'a'
|
request.application = 'a'
|
||||||
request.controller = 'c'
|
request.controller = 'c'
|
||||||
request.function = 'f'
|
request.function = 'f'
|
||||||
request.folder = 'applications/admin'
|
request.folder = 'applications/admin'
|
||||||
response = Response()
|
response = Response()
|
||||||
session = Session()
|
session = Session()
|
||||||
session.connect(request, response)
|
session.connect(request, response)
|
||||||
|
from gluon.globals import current
|
||||||
|
current.request = request
|
||||||
|
current.response = response
|
||||||
|
current.session = session
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
class testRequest(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
from gluon.globals import current
|
from gluon.globals import current
|
||||||
current.request = request
|
current.response = Response()
|
||||||
current.response = response
|
|
||||||
current.session = session
|
def test_restful_simple(self):
|
||||||
return current
|
env = {'request_method': 'GET', 'PATH_INFO': '/welcome/default/index/1.pdf'}
|
||||||
|
r = Request(env)
|
||||||
|
regex_url_in(r, env)
|
||||||
|
|
||||||
|
@r.restful()
|
||||||
|
def simple_rest():
|
||||||
|
def GET(*args, **vars):
|
||||||
|
return args[0]
|
||||||
|
return locals()
|
||||||
|
|
||||||
|
self.assertEqual(simple_rest(), '1')
|
||||||
|
|
||||||
|
def test_restful_calls_post(self):
|
||||||
|
env = {'request_method': 'POST', 'PATH_INFO': '/welcome/default/index'}
|
||||||
|
r = Request(env)
|
||||||
|
regex_url_in(r, env)
|
||||||
|
|
||||||
|
@r.restful()
|
||||||
|
def post_rest():
|
||||||
|
def POST(*args, **vars):
|
||||||
|
return 'I posted'
|
||||||
|
return locals()
|
||||||
|
|
||||||
|
self.assertEqual(post_rest(), 'I posted')
|
||||||
|
|
||||||
|
def test_restful_ignore_extension(self):
|
||||||
|
env = {'request_method': 'GET', 'PATH_INFO': '/welcome/default/index/127.0.0.1'}
|
||||||
|
r = Request(env)
|
||||||
|
regex_url_in(r, env)
|
||||||
|
|
||||||
|
@r.restful(ignore_extension=True)
|
||||||
|
def ignore_rest():
|
||||||
|
def GET(*args, **vars):
|
||||||
|
return args[0]
|
||||||
|
return locals()
|
||||||
|
|
||||||
|
self.assertEqual(ignore_rest(), '127.0.0.1')
|
||||||
|
|
||||||
|
|
||||||
class testResponse(unittest.TestCase):
|
class testResponse(unittest.TestCase):
|
||||||
|
|
||||||
#port from python 2.7, needed for 2.5 and 2.6 tests
|
# port from python 2.7, needed for 2.5 and 2.6 tests
|
||||||
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
||||||
"""Fail the test unless the text matches the regular expression."""
|
"""Fail the test unless the text matches the regular expression."""
|
||||||
if isinstance(expected_regexp, basestring):
|
if isinstance(expected_regexp, basestring):
|
||||||
@@ -99,8 +148,8 @@ class testResponse(unittest.TestCase):
|
|||||||
response.files.append(URL('a', 'static', 'css/file.css'))
|
response.files.append(URL('a', 'static', 'css/file.css'))
|
||||||
content = return_includes(response)
|
content = return_includes(response)
|
||||||
self.assertEqual(content,
|
self.assertEqual(content,
|
||||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||||
|
|
||||||
response = Response()
|
response = Response()
|
||||||
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||||
@@ -109,12 +158,11 @@ class testResponse(unittest.TestCase):
|
|||||||
response.files.append(URL('a', 'static', 'css/file.ts'))
|
response.files.append(URL('a', 'static', 'css/file.ts'))
|
||||||
content = return_includes(response)
|
content = return_includes(response)
|
||||||
self.assertEqual(content,
|
self.assertEqual(content,
|
||||||
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
'<script src="https://code.jquery.com/jquery-1.11.3.min.js?var=0" type="text/javascript"></script>' +
|
||||||
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
|
'<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />' +
|
||||||
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
|
'<script src="/a/static/css/file.ts" type="text/typescript"></script>' +
|
||||||
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
|
'<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
response = Response()
|
response = Response()
|
||||||
response.files.append(URL('a', 'static', 'css/file.js'))
|
response.files.append(URL('a', 'static', 'css/file.js'))
|
||||||
@@ -122,13 +170,13 @@ class testResponse(unittest.TestCase):
|
|||||||
content = return_includes(response, extensions=['css'])
|
content = return_includes(response, extensions=['css'])
|
||||||
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
self.assertEqual(content, '<link href="/a/static/css/file.css" rel="stylesheet" type="text/css" />')
|
||||||
|
|
||||||
#regr test for #628
|
# regr test for #628
|
||||||
response = Response()
|
response = Response()
|
||||||
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
|
response.files.append('http://maps.google.com/maps/api/js?sensor=false')
|
||||||
content = return_includes(response)
|
content = return_includes(response)
|
||||||
self.assertEqual(content, '')
|
self.assertEqual(content, '')
|
||||||
|
|
||||||
#regr test for #628
|
# regr test for #628
|
||||||
response = Response()
|
response = Response()
|
||||||
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
response.files.append(('js', 'http://maps.google.com/maps/api/js?sensor=false'))
|
||||||
content = return_includes(response)
|
content = return_includes(response)
|
||||||
@@ -147,7 +195,7 @@ class testResponse(unittest.TestCase):
|
|||||||
def test_cookies(self):
|
def test_cookies(self):
|
||||||
current = setup_clean_session()
|
current = setup_clean_session()
|
||||||
cookie = str(current.response.cookies)
|
cookie = str(current.response.cookies)
|
||||||
session_key='%s=%s'%(current.response.session_id_name,current.response.session_id)
|
session_key = '%s=%s' % (current.response.session_id_name, current.response.session_id)
|
||||||
self.assertRegexpMatches(cookie, r'^Set-Cookie: ')
|
self.assertRegexpMatches(cookie, r'^Set-Cookie: ')
|
||||||
self.assertTrue(session_key in cookie)
|
self.assertTrue(session_key in cookie)
|
||||||
self.assertTrue('Path=/' in cookie)
|
self.assertTrue('Path=/' in cookie)
|
||||||
|
|||||||
+50
-13
@@ -11,11 +11,13 @@ import unittest
|
|||||||
from gluon.html import A, ASSIGNJS, B, BEAUTIFY, P, BODY, BR, BUTTON, CAT, CENTER, CODE, COL, COLGROUP, DIV, SPAN, URL, verifyURL
|
from gluon.html import A, ASSIGNJS, B, BEAUTIFY, P, BODY, BR, BUTTON, CAT, CENTER, CODE, COL, COLGROUP, DIV, SPAN, URL, verifyURL
|
||||||
from gluon.html import truncate_string, EM, FIELDSET, FORM, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML, I, IFRAME, IMG, INPUT, EMBED
|
from gluon.html import truncate_string, EM, FIELDSET, FORM, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML, I, IFRAME, IMG, INPUT, EMBED
|
||||||
from gluon.html import LABEL, LEGEND, LI, LINK, MARKMIN, MENU, META, OBJECT, OL, OPTGROUP, OPTION, PRE, SCRIPT, SELECT, STRONG
|
from gluon.html import LABEL, LEGEND, LI, LINK, MARKMIN, MENU, META, OBJECT, OL, OPTGROUP, OPTION, PRE, SCRIPT, SELECT, STRONG
|
||||||
from gluon.html import STYLE, TABLE, TR, TD, TAG, TBODY, THEAD, TEXTAREA, TFOOT, TH, TITLE, TT, UL, XHTML, XML
|
from gluon.html import STYLE, TABLE, TR, TD, TAG, TBODY, THEAD, TEXTAREA, TFOOT, TH, TITLE, TT, UL, XHTML, XML, web2pyHTMLParser
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
from gluon.html import XML_pickle, XML_unpickle
|
from gluon.html import XML_pickle, XML_unpickle
|
||||||
from gluon.html import TAG_pickler, TAG_unpickler
|
from gluon.html import TAG_pickler, TAG_unpickler
|
||||||
from gluon._compat import xrange, PY2, to_native
|
from gluon._compat import xrange, PY2, to_native
|
||||||
|
from gluon.decoder import decoder
|
||||||
|
import re
|
||||||
|
|
||||||
class TestBareHelpers(unittest.TestCase):
|
class TestBareHelpers(unittest.TestCase):
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
self.assertEqual(rtn, True)
|
self.assertEqual(rtn, True)
|
||||||
|
|
||||||
# TODO: def test_XmlComponent(self):
|
# TODO: def test_XmlComponent(self):
|
||||||
@unittest.skipIf(not PY2, "Skipping Python 3.x tests for XML.__repr__")
|
|
||||||
def test_XML(self):
|
def test_XML(self):
|
||||||
# sanitization process
|
# sanitization process
|
||||||
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
|
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
|
||||||
@@ -168,6 +170,8 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
# seams that __repr__ is no longer enough
|
# seams that __repr__ is no longer enough
|
||||||
##self.assertEqual(XML('1.3'), '1.3')
|
##self.assertEqual(XML('1.3'), '1.3')
|
||||||
self.assertEqual(XML(u'<div>è</div>').xml(), b'<div>\xc3\xa8</div>')
|
self.assertEqual(XML(u'<div>è</div>').xml(), b'<div>\xc3\xa8</div>')
|
||||||
|
# make sure unicode works with sanitize
|
||||||
|
self.assertEqual(XML(u'<div>è</div>', sanitize=True).xml(), b'<div>\xc3\xa8</div>')
|
||||||
# you can calc len on the class, that equals the xml() and the str()
|
# you can calc len on the class, that equals the xml() and the str()
|
||||||
##self.assertEqual(len(XML('1.3')), len('1.3'))
|
##self.assertEqual(len(XML('1.3')), len('1.3'))
|
||||||
self.assertEqual(len(XML('1.3').xml()), len('1.3'))
|
self.assertEqual(len(XML('1.3').xml()), len('1.3'))
|
||||||
@@ -179,19 +183,18 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
# you can compare them
|
# you can compare them
|
||||||
##self.assertEqual(XML('a') == XML('a'), True)
|
##self.assertEqual(XML('a') == XML('a'), True)
|
||||||
# beware that the comparison is made on the XML repr
|
# beware that the comparison is made on the XML repr
|
||||||
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
|
|
||||||
XML('<h1>HelloWorld</h1>'))
|
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True).__repr__(),
|
||||||
|
XML('<h1>HelloWorld</h1>').__repr__())
|
||||||
# bug check for the sanitizer for closing no-close tags
|
# bug check for the sanitizer for closing no-close tags
|
||||||
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
|
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True).xml(),
|
||||||
XML('<p>Test</p><br /><p>Test</p><br />'))
|
XML('<p>Test</p><br /><p>Test</p><br />').xml())
|
||||||
# basic flatten test
|
# basic flatten test
|
||||||
self.assertEqual(XML('<p>Test</p>').flatten(), '<p>Test</p>')
|
self.assertEqual(XML('<p>Test</p>').flatten(), '<p>Test</p>')
|
||||||
self.assertEqual(XML('<p>Test</p>').flatten(render=lambda text, tag, attr: text), '<p>Test</p>')
|
self.assertEqual(XML('<p>Test</p>').flatten(render=lambda text, tag, attr: text), '<p>Test</p>')
|
||||||
|
|
||||||
@unittest.skipIf(not PY2, "Skipping Python 3.x tests for XML_unpickle.__repr__")
|
|
||||||
def test_XML_pickle_unpickle(self):
|
def test_XML_pickle_unpickle(self):
|
||||||
# weird test
|
self.assertEqual(str(XML_unpickle(XML_pickle('data to be pickle')[1][0])), 'data to be pickle')
|
||||||
self.assertEqual(XML_unpickle(XML_pickle('data to be pickle')[1][0]), 'data to be pickle')
|
|
||||||
|
|
||||||
def test_DIV(self):
|
def test_DIV(self):
|
||||||
# Empty DIV()
|
# Empty DIV()
|
||||||
@@ -255,6 +258,11 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
self.assertEqual(DIV('<p>Test</p>', _class="class_test").get('_class'), 'class_test')
|
self.assertEqual(DIV('<p>Test</p>', _class="class_test").get('_class'), 'class_test')
|
||||||
self.assertEqual(DIV(b'a').xml(), b'<div>a</div>')
|
self.assertEqual(DIV(b'a').xml(), b'<div>a</div>')
|
||||||
|
|
||||||
|
def test_decoder(self):
|
||||||
|
tag_html = '<div><span><a id="1-1" u:v="$">hello</a></span><p class="this is a test">world</p></div>'
|
||||||
|
a = decoder(tag_html)
|
||||||
|
self.assertEqual(a, tag_html)
|
||||||
|
|
||||||
def test_CAT(self):
|
def test_CAT(self):
|
||||||
# Empty CAT()
|
# Empty CAT()
|
||||||
self.assertEqual(CAT().xml(), b'')
|
self.assertEqual(CAT().xml(), b'')
|
||||||
@@ -636,8 +644,8 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
# These 2 crash AppVeyor and Travis with: "ImportError: No YAML serializer available"
|
# These 2 crash AppVeyor and Travis with: "ImportError: No YAML serializer available"
|
||||||
# self.assertEqual(FORM('<>', _a='1', _b='2').as_yaml(),
|
# self.assertEqual(FORM('<>', _a='1', _b='2').as_yaml(),
|
||||||
# "accepted: null\nattributes: {_a: '1', _action: '#', _b: '2', _enctype: multipart/form-data, _method: post}\ncomponents: [<>]\nerrors: {}\nlatest: {}\nparent: null\nvars: {}\n")
|
# "accepted: null\nattributes: {_a: '1', _action: '#', _b: '2', _enctype: multipart/form-data, _method: post}\ncomponents: [<>]\nerrors: {}\nlatest: {}\nparent: null\nvars: {}\n")
|
||||||
# self.assertEqual(FORM('<>', _a='1', _b='2').as_xml(),
|
# TODO check tags content
|
||||||
# '<?xml version="1.0" encoding="UTF-8"?><document><errors></errors><vars></vars><parent>None</parent><attributes><_enctype>multipart/form-data</_enctype><_action>#</_action><_b>2</_b><_a>1</_a><_method>post</_method></attributes><components><item>&lt;&gt;</item></components><accepted>None</accepted><latest></latest></document>')
|
self.assertEqual(len(FORM('<>', _a='1', _b='2').as_xml()), 334)
|
||||||
|
|
||||||
def test_BEAUTIFY(self):
|
def test_BEAUTIFY(self):
|
||||||
#self.assertEqual(BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml(),
|
#self.assertEqual(BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml(),
|
||||||
@@ -670,13 +678,42 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
|
|
||||||
# TODO: def test_embed64(self):
|
# TODO: def test_embed64(self):
|
||||||
|
|
||||||
# TODO: def test_web2pyHTMLParser(self):
|
def test_web2pyHTMLParser(self):
|
||||||
|
#tag should not be a byte
|
||||||
|
self.assertEqual(web2pyHTMLParser("<div></div>").tree.components[0].tag, 'div')
|
||||||
|
a = str(web2pyHTMLParser('<div>a<span>b</div>c').tree)
|
||||||
|
self.assertEqual(a, "<div>a<span>b</span></div>c")
|
||||||
|
|
||||||
|
tree = web2pyHTMLParser('hello<div a="b">world</div>').tree
|
||||||
|
tree.element(_a='b')['_c']=5
|
||||||
|
self.assertEqual(str(tree), 'hello<div a="b" c="5">world</div>')
|
||||||
|
|
||||||
|
a = str(web2pyHTMLParser('<div><img class="img"/></div>', closed=['img']).tree)
|
||||||
|
self.assertEqual(a, '<div><img class="img" /></div>')
|
||||||
|
|
||||||
|
#greater-than sign ( > ) --> decimal > --> hexadecimal >
|
||||||
|
#Less-than sign ( < ) --> decimal < --> hexadecimal <
|
||||||
|
# test decimal
|
||||||
|
a = str(web2pyHTMLParser('<div>< ></div>').tree)
|
||||||
|
self.assertEqual(a, '<div>< ></div>')
|
||||||
|
# test hexadecimal
|
||||||
|
a = str(web2pyHTMLParser('<div>< ></div>').tree)
|
||||||
|
self.assertEqual(a, '<div>< ></div>')
|
||||||
|
|
||||||
|
def test_markdown(self):
|
||||||
|
def markdown(text, tag=None, attributes={}):
|
||||||
|
r = {None: re.sub('\s+',' ',text), \
|
||||||
|
'h1':'#'+text+'\\n\\n', \
|
||||||
|
'p':text+'\\n'}.get(tag,text)
|
||||||
|
return r
|
||||||
|
a=TAG('<h1>Header</h1><p>this is a test</p>')
|
||||||
|
ret = a.flatten(markdown)
|
||||||
|
self.assertEqual(ret, '#Header\\n\\nthis is a test\\n')
|
||||||
|
|
||||||
# TODO: def test_markdown_serializer(self):
|
# TODO: def test_markdown_serializer(self):
|
||||||
|
|
||||||
# TODO: def test_markmin_serializer(self):
|
# TODO: def test_markmin_serializer(self):
|
||||||
|
|
||||||
@unittest.skipIf(not PY2, "Skipping Python 3.x tests for MARKMIN")
|
|
||||||
def test_MARKMIN(self):
|
def test_MARKMIN(self):
|
||||||
# This test pass with python 2.7 but expected to fail under 2.6
|
# This test pass with python 2.7 but expected to fail under 2.6
|
||||||
# with self.assertRaises(TypeError) as cm:
|
# with self.assertRaises(TypeError) as cm:
|
||||||
|
|||||||
+39
-39
@@ -586,81 +586,81 @@ class TestUnicode(unittest.TestCase):
|
|||||||
# disables prepending the scheme in the return value
|
# disables prepending the scheme in the return value
|
||||||
|
|
||||||
def testUnicodeToAsciiUrl(self):
|
def testUnicodeToAsciiUrl(self):
|
||||||
self.assertEquals(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu')
|
self.assertEqual(unicode_to_ascii_authority(u'www.Alliancefran\xe7aise.nu'), 'www.xn--alliancefranaise-npb.nu')
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
unicode_to_ascii_authority(u'www.benn.ca'), 'www.benn.ca')
|
unicode_to_ascii_authority(u'www.benn.ca'), 'www.benn.ca')
|
||||||
self.assertRaises(UnicodeError, unicode_to_ascii_authority,
|
self.assertRaises(UnicodeError, unicode_to_ascii_authority,
|
||||||
u'\u4e2d' * 1000) # label is too long
|
u'\u4e2d' * 1000) # label is too long
|
||||||
|
|
||||||
def testValidUrls(self):
|
def testValidUrls(self):
|
||||||
self.assertEquals(self.x(u'www.Alliancefrancaise.nu'), (
|
self.assertEqual(self.x(u'www.Alliancefrancaise.nu'), (
|
||||||
'http://www.Alliancefrancaise.nu', None))
|
'http://www.Alliancefrancaise.nu', None))
|
||||||
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu'), (
|
self.assertEqual(self.x(u'www.Alliancefran\xe7aise.nu'), (
|
||||||
'http://www.xn--alliancefranaise-npb.nu', None))
|
'http://www.xn--alliancefranaise-npb.nu', None))
|
||||||
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu:8080'), (
|
self.assertEqual(self.x(u'www.Alliancefran\xe7aise.nu:8080'), (
|
||||||
'http://www.xn--alliancefranaise-npb.nu:8080', None))
|
'http://www.xn--alliancefranaise-npb.nu:8080', None))
|
||||||
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu'),
|
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu'),
|
||||||
('http://www.xn--alliancefranaise-npb.nu', None))
|
('http://www.xn--alliancefranaise-npb.nu', None))
|
||||||
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None))
|
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue', None))
|
||||||
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None))
|
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue#fragment', None))
|
||||||
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
|
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
|
||||||
self.assertEquals(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None))
|
self.assertEqual(self.x(u'http://www.Alliancefran\xe7aise.nu:8080/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu:8080/parnaise/blue?query=value#fragment', None))
|
||||||
self.assertEquals(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
|
self.assertEqual(self.x(u'www.Alliancefran\xe7aise.nu/parnaise/blue?query=value#fragment'), ('http://www.xn--alliancefranaise-npb.nu/parnaise/blue?query=value#fragment', None))
|
||||||
self.assertEquals(self.x(
|
self.assertEqual(self.x(
|
||||||
u'http://\u4e2d\u4fd4.com'), ('http://xn--fiq13b.com', None))
|
u'http://\u4e2d\u4fd4.com'), ('http://xn--fiq13b.com', None))
|
||||||
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'),
|
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com/\u4e86'),
|
||||||
('http://xn--fiq13b.com/%4e%86', None))
|
('http://xn--fiq13b.com/%4e%86', None))
|
||||||
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None))
|
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86', None))
|
||||||
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None))
|
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com/\u4e86?query=\u4e86#fragment'), ('http://xn--fiq13b.com/%4e%86?query=%4e%86#fragment', None))
|
||||||
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None))
|
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.com?query=\u4e86#fragment'), ('http://xn--fiq13b.com?query=%4e%86#fragment', None))
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.x(u'http://B\xfccher.ch'), ('http://xn--bcher-kva.ch', None))
|
self.x(u'http://B\xfccher.ch'), ('http://xn--bcher-kva.ch', None))
|
||||||
self.assertEquals(self.x(u'http://\xe4\xf6\xfc\xdf.com'), (
|
self.assertEqual(self.x(u'http://\xe4\xf6\xfc\xdf.com'), (
|
||||||
'http://xn--ss-uia6e4a.com', None))
|
'http://xn--ss-uia6e4a.com', None))
|
||||||
self.assertEquals(self.x(
|
self.assertEqual(self.x(
|
||||||
u'http://visegr\xe1d.com'), ('http://xn--visegrd-mwa.com', None))
|
u'http://visegr\xe1d.com'), ('http://xn--visegrd-mwa.com', None))
|
||||||
self.assertEquals(self.x(u'http://h\xe1zipatika.com'), (
|
self.assertEqual(self.x(u'http://h\xe1zipatika.com'), (
|
||||||
'http://xn--hzipatika-01a.com', None))
|
'http://xn--hzipatika-01a.com', None))
|
||||||
self.assertEquals(self.x(u'http://www.\xe7ukurova.com'), (
|
self.assertEqual(self.x(u'http://www.\xe7ukurova.com'), (
|
||||||
'http://www.xn--ukurova-txa.com', None))
|
'http://www.xn--ukurova-txa.com', None))
|
||||||
self.assertEquals(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None))
|
self.assertEqual(self.x(u'http://nixier\xf6hre.nixieclock-tube.com'), ('http://xn--nixierhre-57a.nixieclock-tube.com', None))
|
||||||
self.assertEquals(self.x(u'google.ca.'), ('http://google.ca.', None))
|
self.assertEqual(self.x(u'google.ca.'), ('http://google.ca.', None))
|
||||||
|
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.y(u'https://google.ca'), ('https://google.ca', None))
|
self.y(u'https://google.ca'), ('https://google.ca', None))
|
||||||
self.assertEquals(self.y(
|
self.assertEqual(self.y(
|
||||||
u'https://\u4e2d\u4fd4.com'), ('https://xn--fiq13b.com', None))
|
u'https://\u4e2d\u4fd4.com'), ('https://xn--fiq13b.com', None))
|
||||||
|
|
||||||
self.assertEquals(self.z(u'google.ca'), ('google.ca', None))
|
self.assertEqual(self.z(u'google.ca'), ('google.ca', None))
|
||||||
|
|
||||||
def testInvalidUrls(self):
|
def testInvalidUrls(self):
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.x(u'://ABC.com'), (u'://ABC.com', 'Enter a valid URL'))
|
self.x(u'://ABC.com'), (u'://ABC.com', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.x(u'http://\u4e2d\u4fd4.dne'), (
|
self.assertEqual(self.x(u'http://\u4e2d\u4fd4.dne'), (
|
||||||
u'http://\u4e2d\u4fd4.dne', 'Enter a valid URL'))
|
u'http://\u4e2d\u4fd4.dne', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.x(u'https://google.dne'), (
|
self.assertEqual(self.x(u'https://google.dne'), (
|
||||||
u'https://google.dne', 'Enter a valid URL'))
|
u'https://google.dne', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.x(u'https://google..ca'), (
|
self.assertEqual(self.x(u'https://google..ca'), (
|
||||||
u'https://google..ca', 'Enter a valid URL'))
|
u'https://google..ca', 'Enter a valid URL'))
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
self.x(u'google..ca'), (u'google..ca', 'Enter a valid URL'))
|
self.x(u'google..ca'), (u'google..ca', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.x(u'http://' + u'\u4e2d' * 1000 + u'.com'), (
|
self.assertEqual(self.x(u'http://' + u'\u4e2d' * 1000 + u'.com'), (
|
||||||
u'http://' + u'\u4e2d' * 1000 + u'.com', 'Enter a valid URL'))
|
u'http://' + u'\u4e2d' * 1000 + u'.com', 'Enter a valid URL'))
|
||||||
|
|
||||||
self.assertEquals(self.x(u'http://google.com#fragment_\u4e86'), (
|
self.assertEqual(self.x(u'http://google.com#fragment_\u4e86'), (
|
||||||
u'http://google.com#fragment_\u4e86', 'Enter a valid URL'))
|
u'http://google.com#fragment_\u4e86', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.x(u'http\u4e86://google.com'), (
|
self.assertEqual(self.x(u'http\u4e86://google.com'), (
|
||||||
u'http\u4e86://google.com', 'Enter a valid URL'))
|
u'http\u4e86://google.com', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (
|
self.assertEqual(self.x(u'http\u4e86://google.com#fragment_\u4e86'), (
|
||||||
u'http\u4e86://google.com#fragment_\u4e86', 'Enter a valid URL'))
|
u'http\u4e86://google.com#fragment_\u4e86', 'Enter a valid URL'))
|
||||||
|
|
||||||
self.assertEquals(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (
|
self.assertEqual(self.y(u'http://\u4e2d\u4fd4.com/\u4e86'), (
|
||||||
u'http://\u4e2d\u4fd4.com/\u4e86', 'Enter a valid URL'))
|
u'http://\u4e2d\u4fd4.com/\u4e86', 'Enter a valid URL'))
|
||||||
#self.assertEquals(self.y(u'google.ca'), (u'google.ca', 'Enter a valid URL'))
|
#self.assertEqual(self.y(u'google.ca'), (u'google.ca', 'Enter a valid URL'))
|
||||||
|
|
||||||
self.assertEquals(self.z(u'invalid.domain..com'), (
|
self.assertEqual(self.z(u'invalid.domain..com'), (
|
||||||
u'invalid.domain..com', 'Enter a valid URL'))
|
u'invalid.domain..com', 'Enter a valid URL'))
|
||||||
self.assertEquals(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (
|
self.assertEqual(self.z(u'invalid.\u4e2d\u4fd4.blargg'), (
|
||||||
u'invalid.\u4e2d\u4fd4.blargg', 'Enter a valid URL'))
|
u'invalid.\u4e2d\u4fd4.blargg', 'Enter a valid URL'))
|
||||||
|
|
||||||
# ##############################################################################
|
# ##############################################################################
|
||||||
|
|||||||
@@ -95,14 +95,14 @@ class TestRouter(unittest.TestCase):
|
|||||||
""" Test router syntax error """
|
""" Test router syntax error """
|
||||||
level = logger.getEffectiveLevel()
|
level = logger.getEffectiveLevel()
|
||||||
logger.setLevel(logging.CRITICAL) # disable logging temporarily
|
logger.setLevel(logging.CRITICAL) # disable logging temporarily
|
||||||
self.assertRaises(SyntaxError, load, data='x:y')
|
self.assertRaises(SyntaxError, load, data='x::y')
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
SyntaxError, load, rdict=dict(BASE=dict(badkey="value")))
|
SyntaxError, load, rdict=dict(BASE=dict(badkey="value")))
|
||||||
self.assertRaises(SyntaxError, load, rdict=dict(
|
self.assertRaises(SyntaxError, load, rdict=dict(
|
||||||
BASE=dict(), app=dict(default_application="name")))
|
BASE=dict(), app=dict(default_application="name")))
|
||||||
|
|
||||||
self.myassertRaisesRegex(SyntaxError, "invalid syntax",
|
self.myassertRaisesRegex(SyntaxError, "invalid syntax",
|
||||||
load, data='x:y')
|
load, data='x::y')
|
||||||
self.myassertRaisesRegex(SyntaxError, "unknown key",
|
self.myassertRaisesRegex(SyntaxError, "unknown key",
|
||||||
load, rdict=dict(BASE=dict(badkey="value")))
|
load, rdict=dict(BASE=dict(badkey="value")))
|
||||||
self.myassertRaisesRegex(SyntaxError, "BASE-only key",
|
self.myassertRaisesRegex(SyntaxError, "BASE-only key",
|
||||||
@@ -873,6 +873,27 @@ class TestRouter(unittest.TestCase):
|
|||||||
self.assertEqual(str(URL(a='app2', c='default', f='index',
|
self.assertEqual(str(URL(a='app2', c='default', f='index',
|
||||||
args=['ctr'])), "/app2/index/ctr")
|
args=['ctr'])), "/app2/index/ctr")
|
||||||
|
|
||||||
|
# outbound - with extensions
|
||||||
|
self.assertEqual(
|
||||||
|
str(URL(a='app', c='default', f='index.json')), "/index.json")
|
||||||
|
self.assertEqual(
|
||||||
|
str(URL(a='app', c='default', f='index.json', args=['arg1'])), "/index.json/arg1")
|
||||||
|
self.assertEqual(str(
|
||||||
|
URL(a='app', c='default', f='user.json')), "/user.json")
|
||||||
|
self.assertEqual(str(
|
||||||
|
URL(a='app', c='default', f='user.json', args=['arg1'])), "/user.json/arg1")
|
||||||
|
self.assertEqual(str(URL(
|
||||||
|
a='app', c='default', f='user.json', args=['index'])), "/user.json/index")
|
||||||
|
self.assertEqual(str(
|
||||||
|
URL(a='app', c='default', f='index.json', args=['ctr'])), "/index.json/ctr")
|
||||||
|
self.assertEqual(
|
||||||
|
str(URL(a='app', c='ctr', f='ctrf1.json', args=['arg'])), "/ctr/ctrf1.json/arg")
|
||||||
|
|
||||||
|
self.assertEqual(str(URL(
|
||||||
|
a='app2', c='default', f='index.json', args=['arg1'])), "/app2/index.json/arg1")
|
||||||
|
self.assertEqual(str(URL(
|
||||||
|
a='app2', c='ctr', f='index.json', args=['arg1'])), "/app2/ctr/index.json/arg1")
|
||||||
|
|
||||||
# inbound
|
# inbound
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
filter_url('http://d.com/arg'), "/app/default/index ['arg']")
|
filter_url('http://d.com/arg'), "/app/default/index ['arg']")
|
||||||
@@ -896,6 +917,30 @@ class TestRouter(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
filter_url('http://d.com/app2/ctr/arg'), "/app2/ctr/arg")
|
filter_url('http://d.com/app2/ctr/arg'), "/app2/ctr/arg")
|
||||||
|
|
||||||
|
# inbound - with extensions
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/index.json'), "/app/default/index.json")
|
||||||
|
self.assertEqual(filter_url('http://d.com/user.json'), "/app/default/user.json")
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/user.json/arg'), "/app/default/user.json ['arg']")
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/user.json/index'), "/app/default/user.json ['index']")
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/index.json/ctr'), "/app/default/index.json ['ctr']")
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/ctr/ctrf1.json/arg'), "/app/ctr/ctrf1.json ['arg']")
|
||||||
|
|
||||||
|
self.assertEqual(filter_url(
|
||||||
|
'http://d.com/app2/index.json/arg'), "/app2/default/index.json ['arg']")
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/app2/user.json'), "/app2/default/user.json")
|
||||||
|
self.assertEqual(filter_url(
|
||||||
|
'http://d.com/app2/user.json/arg'), "/app2/default/user.json ['arg']")
|
||||||
|
self.assertEqual(filter_url(
|
||||||
|
'http://d.com/app2/ctr/index.json/arg'), "/app2/ctr/index.json ['arg']")
|
||||||
|
self.assertEqual(
|
||||||
|
filter_url('http://d.com/app2/ctr/arg'), "/app2/ctr/arg")
|
||||||
|
|
||||||
def test_router_functions2(self):
|
def test_router_functions2(self):
|
||||||
'''
|
'''
|
||||||
Test more functions=[something]
|
Test more functions=[something]
|
||||||
|
|||||||
@@ -235,6 +235,11 @@ class TestValidators(unittest.TestCase):
|
|||||||
self.assertEqual(sorted(rtn), [('%d' % george_id, 'george'), ('%d' % costanza_id, 'costanza')])
|
self.assertEqual(sorted(rtn), [('%d' % george_id, 'george'), ('%d' % costanza_id, 'costanza')])
|
||||||
rtn = IS_IN_DB(db, db.person.id, db.person.name, error_message='oops', sort=True).options(zero=True)
|
rtn = IS_IN_DB(db, db.person.id, db.person.name, error_message='oops', sort=True).options(zero=True)
|
||||||
self.assertEqual(rtn, [('', ''), ('%d' % costanza_id, 'costanza'), ('%d' % george_id, 'george')])
|
self.assertEqual(rtn, [('', ''), ('%d' % costanza_id, 'costanza'), ('%d' % george_id, 'george')])
|
||||||
|
# Test None
|
||||||
|
rtn = IS_IN_DB(db, 'person.id', '%(name)s', error_message='oops')(None)
|
||||||
|
self.assertEqual(rtn, (None, 'oops'))
|
||||||
|
rtn = IS_IN_DB(db, 'person.name', '%(name)s', error_message='oops')(None)
|
||||||
|
self.assertEqual(rtn, (None, 'oops'))
|
||||||
# Test using the set it made for options
|
# Test using the set it made for options
|
||||||
vldtr = IS_IN_DB(db, 'person.name', '%(name)s', error_message='oops')
|
vldtr = IS_IN_DB(db, 'person.name', '%(name)s', error_message='oops')
|
||||||
vldtr.options()
|
vldtr.options()
|
||||||
@@ -434,21 +439,21 @@ class TestValidators(unittest.TestCase):
|
|||||||
rtn = IS_NOT_EMPTY()('x')
|
rtn = IS_NOT_EMPTY()('x')
|
||||||
self.assertEqual(rtn, ('x', None))
|
self.assertEqual(rtn, ('x', None))
|
||||||
rtn = IS_NOT_EMPTY()(' x ')
|
rtn = IS_NOT_EMPTY()(' x ')
|
||||||
self.assertEqual(rtn, ('x', None))
|
self.assertEqual(rtn, (' x ', None))
|
||||||
rtn = IS_NOT_EMPTY()(None)
|
rtn = IS_NOT_EMPTY()(None)
|
||||||
self.assertEqual(rtn, (None, 'Enter a value'))
|
self.assertEqual(rtn, (None, 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()('')
|
rtn = IS_NOT_EMPTY()('')
|
||||||
self.assertEqual(rtn, ('', 'Enter a value'))
|
self.assertEqual(rtn, ('', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()(' ')
|
rtn = IS_NOT_EMPTY()(' ')
|
||||||
self.assertEqual(rtn, ('', 'Enter a value'))
|
self.assertEqual(rtn, (' ', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()(' \n\t')
|
rtn = IS_NOT_EMPTY()(' \n\t')
|
||||||
self.assertEqual(rtn, ('', 'Enter a value'))
|
self.assertEqual(rtn, (' \n\t', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY()([])
|
rtn = IS_NOT_EMPTY()([])
|
||||||
self.assertEqual(rtn, ([], 'Enter a value'))
|
self.assertEqual(rtn, ([], 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY(empty_regex='def')('def')
|
rtn = IS_NOT_EMPTY(empty_regex='def')('def')
|
||||||
self.assertEqual(rtn, ('', 'Enter a value'))
|
self.assertEqual(rtn, ('def', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
|
rtn = IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
|
||||||
self.assertEqual(rtn, ('', 'Enter a value'))
|
self.assertEqual(rtn, ('deg', 'Enter a value'))
|
||||||
rtn = IS_NOT_EMPTY(empty_regex='def')('abc')
|
rtn = IS_NOT_EMPTY(empty_regex='def')('abc')
|
||||||
self.assertEqual(rtn, ('abc', None))
|
self.assertEqual(rtn, ('abc', None))
|
||||||
|
|
||||||
@@ -531,6 +536,11 @@ class TestValidators(unittest.TestCase):
|
|||||||
rtn = IS_EMAIL(error_message='oops')(42)
|
rtn = IS_EMAIL(error_message='oops')(42)
|
||||||
self.assertEqual(rtn, (42, 'oops'))
|
self.assertEqual(rtn, (42, 'oops'))
|
||||||
|
|
||||||
|
# test for Internationalized Domain Names, see https://docs.python.org/2/library/codecs.html#module-encodings.idna
|
||||||
|
rtn = IS_EMAIL()('web2py@Alliancefrançaise.nu')
|
||||||
|
self.assertEqual(rtn, ('web2py@Alliancefrançaise.nu', None))
|
||||||
|
|
||||||
|
|
||||||
def test_IS_LIST_OF_EMAILS(self):
|
def test_IS_LIST_OF_EMAILS(self):
|
||||||
emails = ['localguy@localhost', '_Yosemite.Sam@example.com']
|
emails = ['localguy@localhost', '_Yosemite.Sam@example.com']
|
||||||
rtn = IS_LIST_OF_EMAILS()(','.join(emails))
|
rtn = IS_LIST_OF_EMAILS()(','.join(emails))
|
||||||
@@ -702,15 +712,19 @@ class TestValidators(unittest.TestCase):
|
|||||||
|
|
||||||
def test_IS_LOWER(self):
|
def test_IS_LOWER(self):
|
||||||
rtn = IS_LOWER()('ABC')
|
rtn = IS_LOWER()('ABC')
|
||||||
|
self.assertEqual(rtn, ('abc', None))
|
||||||
|
rtn = IS_LOWER()(b'ABC')
|
||||||
self.assertEqual(rtn, (b'abc', None))
|
self.assertEqual(rtn, (b'abc', None))
|
||||||
rtn = IS_LOWER()('Ñ')
|
rtn = IS_LOWER()('Ñ')
|
||||||
self.assertEqual(rtn, (b'\xc3\xb1', None))
|
self.assertEqual(rtn, ('ñ', None))
|
||||||
|
|
||||||
def test_IS_UPPER(self):
|
def test_IS_UPPER(self):
|
||||||
rtn = IS_UPPER()('abc')
|
rtn = IS_UPPER()('abc')
|
||||||
|
self.assertEqual(rtn, ('ABC', None))
|
||||||
|
rtn = IS_UPPER()(b'abc')
|
||||||
self.assertEqual(rtn, (b'ABC', None))
|
self.assertEqual(rtn, (b'ABC', None))
|
||||||
rtn = IS_UPPER()('ñ')
|
rtn = IS_UPPER()('ñ')
|
||||||
self.assertEqual(rtn, (b'\xc3\x91', None))
|
self.assertEqual(rtn, ('Ñ', None))
|
||||||
|
|
||||||
def test_IS_SLUG(self):
|
def test_IS_SLUG(self):
|
||||||
rtn = IS_SLUG()('abc123')
|
rtn = IS_SLUG()('abc123')
|
||||||
@@ -780,7 +794,7 @@ class TestValidators(unittest.TestCase):
|
|||||||
rtn = IS_EMPTY_OR(IS_EMAIL())('abc')
|
rtn = IS_EMPTY_OR(IS_EMAIL())('abc')
|
||||||
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
|
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
|
||||||
rtn = IS_EMPTY_OR(IS_EMAIL())(' abc ')
|
rtn = IS_EMPTY_OR(IS_EMAIL())(' abc ')
|
||||||
self.assertEqual(rtn, ('abc', 'Enter a valid email address'))
|
self.assertEqual(rtn, (' abc ', 'Enter a valid email address'))
|
||||||
rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options(zero=False)
|
rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options(zero=False)
|
||||||
self.assertEqual(rtn, [('', ''), ('id1', 'first label'), ('id2', 'second label')])
|
self.assertEqual(rtn, [('', ''), ('id1', 'first label'), ('id2', 'second label')])
|
||||||
rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options()
|
rtn = IS_EMPTY_OR(IS_IN_SET([('id1', 'first label'), ('id2', 'second label')], zero='zero')).options()
|
||||||
@@ -843,7 +857,7 @@ class TestValidators(unittest.TestCase):
|
|||||||
'|'.join(['Minimum length is 8',
|
'|'.join(['Minimum length is 8',
|
||||||
'Maximum length is 4',
|
'Maximum length is 4',
|
||||||
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
||||||
'Must include at least 1 upper case',
|
'Must include at least 1 uppercase',
|
||||||
'Must include at least 1 number']))
|
'Must include at least 1 number']))
|
||||||
)
|
)
|
||||||
rtn = IS_STRONG(es=True)('abcde')
|
rtn = IS_STRONG(es=True)('abcde')
|
||||||
@@ -851,7 +865,7 @@ class TestValidators(unittest.TestCase):
|
|||||||
('abcde',
|
('abcde',
|
||||||
'|'.join(['Minimum length is 8',
|
'|'.join(['Minimum length is 8',
|
||||||
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
||||||
'Must include at least 1 upper case',
|
'Must include at least 1 uppercase',
|
||||||
'Must include at least 1 number']))
|
'Must include at least 1 number']))
|
||||||
)
|
)
|
||||||
rtn = IS_STRONG(upper=0, lower=0, number=0, es=True)('Abcde1')
|
rtn = IS_STRONG(upper=0, lower=0, number=0, es=True)('Abcde1')
|
||||||
@@ -859,8 +873,8 @@ class TestValidators(unittest.TestCase):
|
|||||||
('Abcde1',
|
('Abcde1',
|
||||||
'|'.join(['Minimum length is 8',
|
'|'.join(['Minimum length is 8',
|
||||||
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|',
|
||||||
'May not include any upper case letters',
|
'May not include any uppercase letters',
|
||||||
'May not include any lower case letters',
|
'May not include any lowercase letters',
|
||||||
'May not include any numbers']))
|
'May not include any numbers']))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1029,12 +1043,11 @@ this is the content of the fake file
|
|||||||
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
|
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
|
||||||
rtn = IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
|
rtn = IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
|
||||||
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', None))
|
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', None))
|
||||||
# TODO:
|
|
||||||
# with py3.ipaddress '2001::126c:8ffa:fe22:b3af' is considered private
|
# with py3.ipaddress '2001::126c:8ffa:fe22:b3af' is considered private
|
||||||
# with py2.ipaddress '2001::126c:8ffa:fe22:b3af' is considered private
|
# with py2.ipaddress '2001::126c:8ffa:fe22:b3af' is considered private
|
||||||
# with gluon.contrib.ipaddr(both current and trunk) is not considered private
|
# with gluon.contrib.ipaddr(both current and trunk) is not considered private
|
||||||
# rtn = IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
|
rtn = IS_IPV6(is_routeable=False)('2001::126c:8ffa:fe22:b3af')
|
||||||
# self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', None))
|
self.assertEqual(rtn, ('2001::126c:8ffa:fe22:b3af', None))
|
||||||
rtn = IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
|
rtn = IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
|
||||||
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
|
self.assertEqual(rtn, ('ff00::126c:8ffa:fe22:b3af', 'Enter valid IPv6 address'))
|
||||||
rtn = IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af')
|
rtn = IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af')
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class TestWeb(LiveTest):
|
|||||||
self.assertTrue('Welcome Homer' in client.text)
|
self.assertTrue('Welcome Homer' in client.text)
|
||||||
|
|
||||||
client = WebClient('http://127.0.0.1:8000/admin/default/')
|
client = WebClient('http://127.0.0.1:8000/admin/default/')
|
||||||
client.post('index', data=dict(password='hello'))
|
client.post('index', data=dict(password='testpass'))
|
||||||
client.get('site')
|
client.get('site')
|
||||||
client.get('design/welcome')
|
client.get('design/welcome')
|
||||||
|
|
||||||
|
|||||||
+356
-892
File diff suppressed because it is too large
Load Diff
+20
-18
@@ -17,6 +17,7 @@ import random
|
|||||||
import inspect
|
import inspect
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
@@ -155,12 +156,12 @@ def get_digest(value):
|
|||||||
raise ValueError("Invalid digest algorithm: %s" % value)
|
raise ValueError("Invalid digest algorithm: %s" % value)
|
||||||
|
|
||||||
DIGEST_ALG_BY_SIZE = {
|
DIGEST_ALG_BY_SIZE = {
|
||||||
128 / 4: 'md5',
|
128 // 4: 'md5',
|
||||||
160 / 4: 'sha1',
|
160 // 4: 'sha1',
|
||||||
224 / 4: 'sha224',
|
224 // 4: 'sha224',
|
||||||
256 / 4: 'sha256',
|
256 // 4: 'sha256',
|
||||||
384 / 4: 'sha384',
|
384 // 4: 'sha384',
|
||||||
512 / 4: 'sha512',
|
512 // 4: 'sha512',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -299,19 +300,20 @@ def initialize_urandom():
|
|||||||
try:
|
try:
|
||||||
os.urandom(1)
|
os.urandom(1)
|
||||||
have_urandom = True
|
have_urandom = True
|
||||||
try:
|
if sys.platform != 'win32':
|
||||||
# try to add process-specific entropy
|
|
||||||
frandom = open('/dev/urandom', 'wb')
|
|
||||||
try:
|
try:
|
||||||
if PY2:
|
# try to add process-specific entropy
|
||||||
frandom.write(''.join(chr(t) for t in ctokens))
|
frandom = open('/dev/urandom', 'wb')
|
||||||
else:
|
try:
|
||||||
frandom.write(bytes([]).join(bytes([t]) for t in ctokens))
|
if PY2:
|
||||||
finally:
|
frandom.write(''.join(chr(t) for t in ctokens))
|
||||||
frandom.close()
|
else:
|
||||||
except IOError:
|
frandom.write(bytes([]).join(bytes([t]) for t in ctokens))
|
||||||
# works anyway
|
finally:
|
||||||
pass
|
frandom.close()
|
||||||
|
except IOError:
|
||||||
|
# works anyway
|
||||||
|
pass
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
have_urandom = False
|
have_urandom = False
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|||||||
+143
-123
@@ -10,7 +10,6 @@
|
|||||||
Validators
|
Validators
|
||||||
-----------
|
-----------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
@@ -21,7 +20,9 @@ import urllib
|
|||||||
import struct
|
import struct
|
||||||
import decimal
|
import decimal
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from gluon._compat import StringIO, long, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, to_unicode, to_native
|
|
||||||
|
from gluon._compat import StringIO, long, basestring, unicodeT, to_unicode, urllib_unquote, unichr, to_bytes, PY2, \
|
||||||
|
to_unicode, to_native, string_types, urlparse
|
||||||
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
||||||
from pydal.objects import Field, FieldVirtual, FieldMethod
|
from pydal.objects import Field, FieldVirtual, FieldMethod
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
@@ -195,7 +196,7 @@ class IS_MATCH(Validator):
|
|||||||
self.is_unicode = is_unicode or (not(PY2))
|
self.is_unicode = is_unicode or (not(PY2))
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if not(PY2): # PY3 convert bytes to unicode
|
if not(PY2): # PY3 convert bytes to unicode
|
||||||
value = to_unicode(value)
|
value = to_unicode(value)
|
||||||
|
|
||||||
if self.is_unicode or not(PY2):
|
if self.is_unicode or not(PY2):
|
||||||
@@ -270,7 +271,7 @@ class IS_EXPR(Validator):
|
|||||||
return (value, self.expression(value))
|
return (value, self.expression(value))
|
||||||
# for backward compatibility
|
# for backward compatibility
|
||||||
self.environment.update(value=value)
|
self.environment.update(value=value)
|
||||||
exec ('__ret__=' + self.expression, self.environment)
|
exec('__ret__=' + self.expression, self.environment)
|
||||||
if self.environment['__ret__']:
|
if self.environment['__ret__']:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
@@ -452,10 +453,10 @@ class IS_IN_SET(Validator):
|
|||||||
if not self.labels:
|
if not self.labels:
|
||||||
items = [(k, k) for (i, k) in enumerate(self.theset)]
|
items = [(k, k) for (i, k) in enumerate(self.theset)]
|
||||||
else:
|
else:
|
||||||
items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
|
items = [(k, list(self.labels)[i]) for (i, k) in enumerate(self.theset)]
|
||||||
if self.sort:
|
if self.sort:
|
||||||
items.sort(key=lambda o: str(o[1]).upper())
|
items.sort(key=lambda o: str(o[1]).upper())
|
||||||
if zero and not self.zero is None and not self.multiple:
|
if zero and self.zero is not None and not self.multiple:
|
||||||
items.insert(0, ('', self.zero))
|
items.insert(0, ('', self.zero))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@@ -659,7 +660,7 @@ class IS_IN_DB(Validator):
|
|||||||
return (values, None)
|
return (values, None)
|
||||||
else:
|
else:
|
||||||
if field.type in ('id', 'integer'):
|
if field.type in ('id', 'integer'):
|
||||||
if isinstance(value, (int, long)) or value.isdigit():
|
if isinstance(value, (int, long)) or (isinstance(value, string_types) and value.isdigit()):
|
||||||
value = int(value)
|
value = int(value)
|
||||||
elif self.auto_add:
|
elif self.auto_add:
|
||||||
value = self.maybe_add(table, self.fieldnames[0], value)
|
value = self.maybe_add(table, self.fieldnames[0], value)
|
||||||
@@ -823,7 +824,7 @@ class IS_INT_IN_RANGE(Validator):
|
|||||||
|
|
||||||
def str2dec(number):
|
def str2dec(number):
|
||||||
s = str(number)
|
s = str(number)
|
||||||
if not '.' in s:
|
if '.' not in s:
|
||||||
s += '.00'
|
s += '.00'
|
||||||
else:
|
else:
|
||||||
s += '0' * (2 - len(s.split('.')[1]))
|
s += '0' * (2 - len(s.split('.')[1]))
|
||||||
@@ -989,14 +990,15 @@ class IS_DECIMAL_IN_RANGE(Validator):
|
|||||||
|
|
||||||
|
|
||||||
def is_empty(value, empty_regex=None):
|
def is_empty(value, empty_regex=None):
|
||||||
|
_value = value
|
||||||
"""test empty field"""
|
"""test empty field"""
|
||||||
if isinstance(value, (str, unicodeT)):
|
if isinstance(value, (str, unicodeT)):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if empty_regex is not None and empty_regex.match(value):
|
if empty_regex is not None and empty_regex.match(value):
|
||||||
value = ''
|
value = ''
|
||||||
if value is None or value == '' or value == []:
|
if value is None or value == '' or value == []:
|
||||||
return (value, True)
|
return (_value, True)
|
||||||
return (value, False)
|
return (_value, False)
|
||||||
|
|
||||||
|
|
||||||
class IS_NOT_EMPTY(Validator):
|
class IS_NOT_EMPTY(Validator):
|
||||||
@@ -1156,15 +1158,16 @@ class IS_EMAIL(Validator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
regex = re.compile('''
|
body_regex = re.compile('''
|
||||||
^(?!\.) # name may not begin with a dot
|
^(?!\.) # name may not begin with a dot
|
||||||
(
|
(
|
||||||
[-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
|
[-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
|
||||||
|
|
|
|
||||||
(?<!\.)\. # single dots only
|
(?<!\.)\. # single dots only
|
||||||
)+
|
)+
|
||||||
(?<!\.) # name may not end with a dot
|
(?<!\.)$ # name may not end with a dot
|
||||||
@
|
''', re.VERBOSE | re.IGNORECASE)
|
||||||
|
domain_regex = re.compile('''
|
||||||
(
|
(
|
||||||
localhost
|
localhost
|
||||||
|
|
|
|
||||||
@@ -1196,14 +1199,27 @@ class IS_EMAIL(Validator):
|
|||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
if not(isinstance(value, (basestring, unicodeT))) or not value or '@' not in value:
|
||||||
|
return (value, translate(self.error_message))
|
||||||
|
|
||||||
|
body, domain = value.rsplit('@', 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
match = self.regex.match(value)
|
match_body = self.body_regex.match(body)
|
||||||
except TypeError:
|
match_domain = self.domain_regex.match(domain)
|
||||||
|
|
||||||
|
if not match_domain:
|
||||||
|
# check for Internationalized Domain Names
|
||||||
|
# see https://docs.python.org/2/library/codecs.html#module-encodings.idna
|
||||||
|
domain_encoded = to_unicode(domain).encode('idna').decode('ascii')
|
||||||
|
match_domain = self.domain_regex.match(domain_encoded)
|
||||||
|
|
||||||
|
match = (match_body is not None) and (match_domain is not None)
|
||||||
|
except (TypeError, UnicodeError):
|
||||||
# Value may not be a string where we can look for matches.
|
# Value may not be a string where we can look for matches.
|
||||||
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
|
# Example: we're calling ANY_OF formatter and IS_EMAIL is asked to validate a date.
|
||||||
match = None
|
match = None
|
||||||
if match:
|
if match:
|
||||||
domain = value.split('@')[1]
|
|
||||||
if (not self.banned or not self.banned.match(domain)) \
|
if (not self.banned or not self.banned.match(domain)) \
|
||||||
and (not self.forced or self.forced.match(domain)):
|
and (not self.forced or self.forced.match(domain)):
|
||||||
return (value, None)
|
return (value, None)
|
||||||
@@ -1232,7 +1248,7 @@ class IS_LIST_OF_EMAILS(object):
|
|||||||
f = IS_EMAIL()
|
f = IS_EMAIL()
|
||||||
for email in self.split_emails.findall(value):
|
for email in self.split_emails.findall(value):
|
||||||
error = f(email)[1]
|
error = f(email)[1]
|
||||||
if error and not email in bad_emails:
|
if error and email not in bad_emails:
|
||||||
bad_emails.append(email)
|
bad_emails.append(email)
|
||||||
if not bad_emails:
|
if not bad_emails:
|
||||||
return (value, None)
|
return (value, None)
|
||||||
@@ -1372,19 +1388,6 @@ unofficial_url_schemes = [
|
|||||||
all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
|
all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
|
||||||
http_schemes = [None, 'http', 'https']
|
http_schemes = [None, 'http', 'https']
|
||||||
|
|
||||||
|
|
||||||
# This regex comes from RFC 2396, Appendix B. It's used to split a URL into
|
|
||||||
# its component parts
|
|
||||||
# Here are the regex groups that it extracts:
|
|
||||||
# scheme = group(2)
|
|
||||||
# authority = group(4)
|
|
||||||
# path = group(5)
|
|
||||||
# query = group(7)
|
|
||||||
# fragment = group(9)
|
|
||||||
|
|
||||||
url_split_regex = \
|
|
||||||
re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?')
|
|
||||||
|
|
||||||
# Defined in RFC 3490, Section 3.1, Requirement #1
|
# Defined in RFC 3490, Section 3.1, Requirement #1
|
||||||
# Use this regex to split the authority component of a unicode URL into
|
# Use this regex to split the authority component of a unicode URL into
|
||||||
# its component labels
|
# its component labels
|
||||||
@@ -1454,18 +1457,15 @@ def unicode_to_ascii_authority(authority):
|
|||||||
# We use the ToASCII operation because we are about to put the authority
|
# We use the ToASCII operation because we are about to put the authority
|
||||||
# into an IDN-unaware slot
|
# into an IDN-unaware slot
|
||||||
asciiLabels = []
|
asciiLabels = []
|
||||||
try:
|
import encodings.idna
|
||||||
import encodings.idna
|
for label in labels:
|
||||||
for label in labels:
|
if label:
|
||||||
if label:
|
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
|
||||||
asciiLabels.append(to_native(encodings.idna.ToASCII(label)))
|
else:
|
||||||
else:
|
# encodings.idna.ToASCII does not accept an empty string, but
|
||||||
# encodings.idna.ToASCII does not accept an empty string, but
|
# it is necessary for us to allow for empty labels so that we
|
||||||
# it is necessary for us to allow for empty labels so that we
|
# don't modify the URL
|
||||||
# don't modify the URL
|
asciiLabels.append('')
|
||||||
asciiLabels.append('')
|
|
||||||
except:
|
|
||||||
asciiLabels = [str(label) for label in labels]
|
|
||||||
# RFC 3490, Section 4, Step 5
|
# RFC 3490, Section 4, Step 5
|
||||||
return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
|
return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
|
||||||
|
|
||||||
@@ -1504,33 +1504,39 @@ def unicode_to_ascii_url(url, prepend_scheme):
|
|||||||
"""
|
"""
|
||||||
# convert the authority component of the URL into an ASCII punycode string,
|
# convert the authority component of the URL into an ASCII punycode string,
|
||||||
# but encode the rest using the regular URI character encoding
|
# but encode the rest using the regular URI character encoding
|
||||||
|
components = urlparse.urlparse(url)
|
||||||
groups = url_split_regex.match(url).groups()
|
prepended = False
|
||||||
# If no authority was found
|
# If no authority was found
|
||||||
if not groups[3]:
|
if not components.netloc:
|
||||||
# Try appending a scheme to see if that fixes the problem
|
# Try appending a scheme to see if that fixes the problem
|
||||||
scheme_to_prepend = prepend_scheme or 'http'
|
scheme_to_prepend = prepend_scheme or 'http'
|
||||||
groups = url_split_regex.match(
|
components = urlparse.urlparse(to_unicode(scheme_to_prepend) + u'://' + url)
|
||||||
to_unicode(scheme_to_prepend) + u'://' + url).groups()
|
prepended = True
|
||||||
|
|
||||||
# if we still can't find the authority
|
# if we still can't find the authority
|
||||||
if not groups[3]:
|
if not components.netloc:
|
||||||
raise Exception('No authority component found, ' +
|
raise Exception('No authority component found, ' +
|
||||||
'could not decode unicode to US-ASCII')
|
'could not decode unicode to US-ASCII')
|
||||||
|
|
||||||
# We're here if we found an authority, let's rebuild the URL
|
# We're here if we found an authority, let's rebuild the URL
|
||||||
scheme = groups[1]
|
scheme = components.scheme
|
||||||
authority = groups[3]
|
authority = components.netloc
|
||||||
path = groups[4] or ''
|
path = components.path
|
||||||
query = groups[5] or ''
|
query = components.query
|
||||||
fragment = groups[7] or ''
|
fragment = components.fragment
|
||||||
|
|
||||||
if prepend_scheme:
|
if prepended:
|
||||||
scheme = str(scheme) + '://'
|
|
||||||
else:
|
|
||||||
scheme = ''
|
scheme = ''
|
||||||
|
|
||||||
return scheme + unicode_to_ascii_authority(authority) +\
|
unparsed = urlparse.urlunparse((scheme,
|
||||||
escape_unicode(path) + escape_unicode(query) + str(fragment)
|
unicode_to_ascii_authority(authority),
|
||||||
|
escape_unicode(path),
|
||||||
|
'',
|
||||||
|
escape_unicode(query),
|
||||||
|
str(fragment)))
|
||||||
|
if unparsed.startswith('//'):
|
||||||
|
unparsed = unparsed[2:] # Remove the // urlunparse puts in the beginning
|
||||||
|
return unparsed
|
||||||
|
|
||||||
|
|
||||||
class IS_GENERIC_URL(Validator):
|
class IS_GENERIC_URL(Validator):
|
||||||
@@ -1591,7 +1597,8 @@ class IS_GENERIC_URL(Validator):
|
|||||||
% (self.prepend_scheme, self.allowed_schemes))
|
% (self.prepend_scheme, self.allowed_schemes))
|
||||||
|
|
||||||
GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$")
|
GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$")
|
||||||
GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$")
|
GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%]+$")
|
||||||
|
URL_FRAGMENT_VALID = re.compile(r"[|A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%]+$")
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""
|
"""
|
||||||
@@ -1603,41 +1610,49 @@ class IS_GENERIC_URL(Validator):
|
|||||||
prepended with prepend_scheme), and tuple[1] is either
|
prepended with prepend_scheme), and tuple[1] is either
|
||||||
None (success!) or the string error_message
|
None (success!) or the string error_message
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
# if the URL does not misuse the '%' character
|
# if we dont have anything or the URL misuses the '%' character
|
||||||
if not self.GENERIC_URL.search(value):
|
|
||||||
# if the URL is only composed of valid characters
|
if not value or self.GENERIC_URL.search(value):
|
||||||
if self.GENERIC_URL_VALID.match(value):
|
return (value, translate(self.error_message))
|
||||||
# Then split up the URL into its components and check on
|
|
||||||
# the scheme
|
if '#' in value:
|
||||||
scheme = url_split_regex.match(value).group(2)
|
url, fragment_part = value.split('#')
|
||||||
# Clean up the scheme before we check it
|
else:
|
||||||
if not scheme is None:
|
url, fragment_part = value, ''
|
||||||
scheme = urllib_unquote(scheme).lower()
|
# if the URL is only composed of valid characters
|
||||||
# If the scheme really exists
|
if self.GENERIC_URL_VALID.match(url) and (not fragment_part or self.URL_FRAGMENT_VALID.match(fragment_part)):
|
||||||
if scheme in self.allowed_schemes:
|
# Then parse the URL into its components and check on
|
||||||
# Then the URL is valid
|
try:
|
||||||
return (value, None)
|
components = urlparse.urlparse(urllib_unquote(value))._asdict()
|
||||||
else:
|
except ValueError:
|
||||||
# else, for the possible case of abbreviated URLs with
|
return (value, translate(self.error_message))
|
||||||
# ports, check to see if adding a valid scheme fixes
|
|
||||||
# the problem (but only do this if it doesn't have
|
# Clean up the scheme before we check it
|
||||||
# one already!)
|
scheme = components['scheme']
|
||||||
if value.find('://') < 0 and None in self.allowed_schemes:
|
if len(scheme) == 0:
|
||||||
schemeToUse = self.prepend_scheme or 'http'
|
scheme = None
|
||||||
prependTest = self.__call__(
|
else:
|
||||||
schemeToUse + '://' + value)
|
scheme = components['scheme'].lower()
|
||||||
# if the prepend test succeeded
|
# If the scheme doesn't really exists
|
||||||
if prependTest[1] is None:
|
if scheme not in self.allowed_schemes or not scheme and ':' in components['path']:
|
||||||
# if prepending in the output is enabled
|
# for the possible case of abbreviated URLs with
|
||||||
if self.prepend_scheme:
|
# ports, check to see if adding a valid scheme fixes
|
||||||
return prependTest
|
# the problem (but only do this if it doesn't have
|
||||||
else:
|
# one already!)
|
||||||
# else return the original,
|
if '://' not in value and None in self.allowed_schemes:
|
||||||
# non-prepended value
|
schemeToUse = self.prepend_scheme or 'http'
|
||||||
return (value, None)
|
prependTest = self.__call__(
|
||||||
except:
|
schemeToUse + '://' + value)
|
||||||
pass
|
# if the prepend test succeeded
|
||||||
|
if prependTest[1] is None:
|
||||||
|
# if prepending in the output is enabled
|
||||||
|
if self.prepend_scheme:
|
||||||
|
return prependTest
|
||||||
|
else:
|
||||||
|
return (value, None)
|
||||||
|
else:
|
||||||
|
return (value, None)
|
||||||
# else the URL is not valid
|
# else the URL is not valid
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
|
|
||||||
@@ -1904,15 +1919,14 @@ class IS_HTTP_URL(Validator):
|
|||||||
(possible prepended with prepend_scheme), and tuple[1] is either
|
(possible prepended with prepend_scheme), and tuple[1] is either
|
||||||
None (success!) or the string error_message
|
None (success!) or the string error_message
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# if the URL passes generic validation
|
# if the URL passes generic validation
|
||||||
x = IS_GENERIC_URL(error_message=self.error_message,
|
x = IS_GENERIC_URL(error_message=self.error_message,
|
||||||
allowed_schemes=self.allowed_schemes,
|
allowed_schemes=self.allowed_schemes,
|
||||||
prepend_scheme=self.prepend_scheme)
|
prepend_scheme=self.prepend_scheme)
|
||||||
if x(value)[1] is None:
|
if x(value)[1] is None:
|
||||||
componentsMatch = url_split_regex.match(value)
|
components = urlparse.urlparse(value)
|
||||||
authority = componentsMatch.group(4)
|
authority = components.netloc
|
||||||
# if there is an authority component
|
# if there is an authority component
|
||||||
if authority:
|
if authority:
|
||||||
# if authority is a valid IP address
|
# if authority is a valid IP address
|
||||||
@@ -1932,7 +1946,7 @@ class IS_HTTP_URL(Validator):
|
|||||||
else:
|
else:
|
||||||
# else this is a relative/abbreviated URL, which will parse
|
# else this is a relative/abbreviated URL, which will parse
|
||||||
# into the URL's path component
|
# into the URL's path component
|
||||||
path = componentsMatch.group(5)
|
path = components.path
|
||||||
# relative case: if this is a valid path (if it starts with
|
# relative case: if this is a valid path (if it starts with
|
||||||
# a slash)
|
# a slash)
|
||||||
if path.startswith('/'):
|
if path.startswith('/'):
|
||||||
@@ -1941,7 +1955,7 @@ class IS_HTTP_URL(Validator):
|
|||||||
else:
|
else:
|
||||||
# abbreviated case: if we haven't already, prepend a
|
# abbreviated case: if we haven't already, prepend a
|
||||||
# scheme and see if it fixes the problem
|
# scheme and see if it fixes the problem
|
||||||
if value.find('://') < 0:
|
if '://' not in value and None in self.allowed_schemes:
|
||||||
schemeToUse = self.prepend_scheme or 'http'
|
schemeToUse = self.prepend_scheme or 'http'
|
||||||
prependTest = self.__call__(schemeToUse
|
prependTest = self.__call__(schemeToUse
|
||||||
+ '://' + value)
|
+ '://' + value)
|
||||||
@@ -2108,7 +2122,6 @@ class IS_URL(Validator):
|
|||||||
# If we are not able to convert the unicode url into a
|
# If we are not able to convert the unicode url into a
|
||||||
# US-ASCII URL, then the URL is not valid
|
# US-ASCII URL, then the URL is not valid
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
|
|
||||||
methodResult = subMethod(asciiValue)
|
methodResult = subMethod(asciiValue)
|
||||||
# if the validation of the US-ASCII version of the value failed
|
# if the validation of the US-ASCII version of the value failed
|
||||||
if not methodResult[1] is None:
|
if not methodResult[1] is None:
|
||||||
@@ -2470,7 +2483,7 @@ class IS_LIST_OF(Validator):
|
|||||||
|
|
||||||
class IS_LOWER(Validator):
|
class IS_LOWER(Validator):
|
||||||
"""
|
"""
|
||||||
Converts to lower case::
|
Converts to lowercase::
|
||||||
|
|
||||||
>>> IS_LOWER()('ABC')
|
>>> IS_LOWER()('ABC')
|
||||||
('abc', None)
|
('abc', None)
|
||||||
@@ -2480,12 +2493,18 @@ class IS_LOWER(Validator):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
return (to_bytes(to_unicode(value).lower()), None)
|
cast_back = lambda x: x
|
||||||
|
if isinstance(value, str):
|
||||||
|
cast_back = to_native
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
cast_back = to_bytes
|
||||||
|
value = to_unicode(value).lower()
|
||||||
|
return (cast_back(value), None)
|
||||||
|
|
||||||
|
|
||||||
class IS_UPPER(Validator):
|
class IS_UPPER(Validator):
|
||||||
"""
|
"""
|
||||||
Converts to upper case::
|
Converts to uppercase::
|
||||||
|
|
||||||
>>> IS_UPPER()('abc')
|
>>> IS_UPPER()('abc')
|
||||||
('ABC', None)
|
('ABC', None)
|
||||||
@@ -2495,7 +2514,13 @@ class IS_UPPER(Validator):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
return (to_bytes(to_unicode(value).upper()), None)
|
cast_back = lambda x: x
|
||||||
|
if isinstance(value, str):
|
||||||
|
cast_back = to_native
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
cast_back = to_bytes
|
||||||
|
value = to_unicode(value).upper()
|
||||||
|
return (cast_back(value), None)
|
||||||
|
|
||||||
|
|
||||||
def urlify(s, maxlen=80, keep_underscores=False):
|
def urlify(s, maxlen=80, keep_underscores=False):
|
||||||
@@ -2602,7 +2627,7 @@ class ANY_OF(Validator):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
for validator in self.subs:
|
for validator in self.subs:
|
||||||
value, error = validator(value)
|
value, error = validator(value)
|
||||||
if error == None:
|
if error is None:
|
||||||
break
|
break
|
||||||
return value, error
|
return value, error
|
||||||
|
|
||||||
@@ -2742,7 +2767,7 @@ class LazyCrypt(object):
|
|||||||
else:
|
else:
|
||||||
digest_alg, key = self.crypt.digest_alg, ''
|
digest_alg, key = self.crypt.digest_alg, ''
|
||||||
if self.crypt.salt:
|
if self.crypt.salt:
|
||||||
if self.crypt.salt == True:
|
if self.crypt.salt is True:
|
||||||
salt = str(web2py_uuid()).replace('-', '')[-16:]
|
salt = str(web2py_uuid()).replace('-', '')[-16:]
|
||||||
else:
|
else:
|
||||||
salt = self.crypt.salt
|
salt = self.crypt.salt
|
||||||
@@ -2827,7 +2852,7 @@ class CRYPT(object):
|
|||||||
Supports standard algorithms
|
Supports standard algorithms
|
||||||
|
|
||||||
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
|
>>> for alg in ('md5','sha1','sha256','sha384','sha512'):
|
||||||
... print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
|
... print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
|
||||||
md5$...$...
|
md5$...$...
|
||||||
sha1$...$...
|
sha1$...$...
|
||||||
sha256$...$...
|
sha256$...$...
|
||||||
@@ -2839,13 +2864,13 @@ class CRYPT(object):
|
|||||||
Supports for pbkdf2
|
Supports for pbkdf2
|
||||||
|
|
||||||
>>> alg = 'pbkdf2(1000,20,sha512)'
|
>>> alg = 'pbkdf2(1000,20,sha512)'
|
||||||
>>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
|
>>> print(str(CRYPT(digest_alg=alg,salt=True)('test')[0]))
|
||||||
pbkdf2(1000,20,sha512)$...$...
|
pbkdf2(1000,20,sha512)$...$...
|
||||||
|
|
||||||
An optional hmac_key can be specified and it is used as salt prefix
|
An optional hmac_key can be specified and it is used as salt prefix
|
||||||
|
|
||||||
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
|
>>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
|
||||||
>>> print a
|
>>> print(a)
|
||||||
md5$...$...
|
md5$...$...
|
||||||
|
|
||||||
Even if the algorithm changes the hash can still be validated
|
Even if the algorithm changes the hash can still be validated
|
||||||
@@ -3025,22 +3050,22 @@ class IS_STRONG(object):
|
|||||||
all_upper = re.findall("[A-Z]", value)
|
all_upper = re.findall("[A-Z]", value)
|
||||||
if self.upper > 0:
|
if self.upper > 0:
|
||||||
if not len(all_upper) >= self.upper:
|
if not len(all_upper) >= self.upper:
|
||||||
failures.append(translate("Must include at least %s upper case")
|
failures.append(translate("Must include at least %s uppercase")
|
||||||
% str(self.upper))
|
% str(self.upper))
|
||||||
else:
|
else:
|
||||||
if len(all_upper) > 0:
|
if len(all_upper) > 0:
|
||||||
failures.append(
|
failures.append(
|
||||||
translate("May not include any upper case letters"))
|
translate("May not include any uppercase letters"))
|
||||||
if isinstance(self.lower, int):
|
if isinstance(self.lower, int):
|
||||||
all_lower = re.findall("[a-z]", value)
|
all_lower = re.findall("[a-z]", value)
|
||||||
if self.lower > 0:
|
if self.lower > 0:
|
||||||
if not len(all_lower) >= self.lower:
|
if not len(all_lower) >= self.lower:
|
||||||
failures.append(translate("Must include at least %s lower case")
|
failures.append(translate("Must include at least %s lowercase")
|
||||||
% str(self.lower))
|
% str(self.lower))
|
||||||
else:
|
else:
|
||||||
if len(all_lower) > 0:
|
if len(all_lower) > 0:
|
||||||
failures.append(
|
failures.append(
|
||||||
translate("May not include any lower case letters"))
|
translate("May not include any lowercase letters"))
|
||||||
if isinstance(self.number, int):
|
if isinstance(self.number, int):
|
||||||
all_number = re.findall("[0-9]", value)
|
all_number = re.findall("[0-9]", value)
|
||||||
if self.number > 0:
|
if self.number > 0:
|
||||||
@@ -3505,10 +3530,7 @@ class IS_IPV6(Validator):
|
|||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
try:
|
from gluon._compat import ipaddress
|
||||||
import ipaddress
|
|
||||||
except ImportError:
|
|
||||||
from gluon.contrib import ipaddr as ipaddress
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ip = ipaddress.IPv6Address(to_unicode(value))
|
ip = ipaddress.IPv6Address(to_unicode(value))
|
||||||
@@ -3732,12 +3754,10 @@ class IS_IPADDRESS(Validator):
|
|||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
try:
|
from gluon._compat import ipaddress
|
||||||
from ipaddress import ip_address as IPAddress
|
IPAddress = ipaddress.ip_address
|
||||||
from ipaddress import IPv6Address, IPv4Address
|
IPv6Address = ipaddress.IPv6Address
|
||||||
except ImportError:
|
IPv4Address = ipaddress.IPv4Address
|
||||||
from gluon.contrib.ipaddr import (IPAddress, IPv4Address,
|
|
||||||
IPv6Address)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ip = IPAddress(to_unicode(value))
|
ip = IPAddress(to_unicode(value))
|
||||||
|
|||||||
+51
-40
@@ -13,7 +13,7 @@ from __future__ import print_function
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
from gluon._compat import StringIO, thread, xrange
|
from gluon._compat import StringIO, thread, xrange, PY2
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
@@ -134,24 +134,29 @@ class web2pyDialog(object):
|
|||||||
def __init__(self, root, options):
|
def __init__(self, root, options):
|
||||||
""" web2pyDialog constructor """
|
""" web2pyDialog constructor """
|
||||||
|
|
||||||
import Tkinter
|
if PY2:
|
||||||
import tkMessageBox
|
import Tkinter as tkinter
|
||||||
|
import tkMessageBox as messagebox
|
||||||
|
else:
|
||||||
|
import tkinter
|
||||||
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
|
||||||
bg_color = 'white'
|
bg_color = 'white'
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
|
|
||||||
self.root = Tkinter.Toplevel(root, bg=bg_color)
|
self.root = tkinter.Toplevel(root, bg=bg_color)
|
||||||
self.root.resizable(0, 0)
|
self.root.resizable(0, 0)
|
||||||
self.root.title(ProgramName)
|
self.root.title(ProgramName)
|
||||||
|
|
||||||
self.options = options
|
self.options = options
|
||||||
self.scheduler_processes = {}
|
self.scheduler_processes = {}
|
||||||
self.menu = Tkinter.Menu(self.root)
|
self.menu = tkinter.Menu(self.root)
|
||||||
servermenu = Tkinter.Menu(self.menu, tearoff=0)
|
servermenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
||||||
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
||||||
if os.path.exists(iconphoto):
|
if os.path.exists(iconphoto):
|
||||||
img = Tkinter.PhotoImage(file=iconphoto)
|
img = tkinter.PhotoImage(file=iconphoto)
|
||||||
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
|
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
|
||||||
# Building the Menu
|
# Building the Menu
|
||||||
item = lambda: start_browser(httplog)
|
item = lambda: start_browser(httplog)
|
||||||
@@ -163,16 +168,16 @@ class web2pyDialog(object):
|
|||||||
|
|
||||||
self.menu.add_cascade(label='Server', menu=servermenu)
|
self.menu.add_cascade(label='Server', menu=servermenu)
|
||||||
|
|
||||||
self.pagesmenu = Tkinter.Menu(self.menu, tearoff=0)
|
self.pagesmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
|
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
|
||||||
|
|
||||||
#scheduler menu
|
#scheduler menu
|
||||||
self.schedmenu = Tkinter.Menu(self.menu, tearoff=0)
|
self.schedmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
|
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
|
||||||
#start and register schedulers from options
|
#start and register schedulers from options
|
||||||
self.update_schedulers(start=True)
|
self.update_schedulers(start=True)
|
||||||
|
|
||||||
helpmenu = Tkinter.Menu(self.menu, tearoff=0)
|
helpmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||||
|
|
||||||
# Home Page
|
# Home Page
|
||||||
item = lambda: start_browser('http://www.web2py.com/')
|
item = lambda: start_browser('http://www.web2py.com/')
|
||||||
@@ -180,7 +185,7 @@ class web2pyDialog(object):
|
|||||||
command=item)
|
command=item)
|
||||||
|
|
||||||
# About
|
# About
|
||||||
item = lambda: tkMessageBox.showinfo('About web2py', ProgramInfo)
|
item = lambda: messagebox.showinfo('About web2py', ProgramInfo)
|
||||||
helpmenu.add_command(label='About',
|
helpmenu.add_command(label='About',
|
||||||
command=item)
|
command=item)
|
||||||
|
|
||||||
@@ -194,10 +199,10 @@ class web2pyDialog(object):
|
|||||||
else:
|
else:
|
||||||
self.root.protocol('WM_DELETE_WINDOW', self.quit)
|
self.root.protocol('WM_DELETE_WINDOW', self.quit)
|
||||||
|
|
||||||
sticky = Tkinter.NW
|
sticky = tkinter.NW
|
||||||
|
|
||||||
# Prepare the logo area
|
# Prepare the logo area
|
||||||
self.logoarea = Tkinter.Canvas(self.root,
|
self.logoarea = tkinter.Canvas(self.root,
|
||||||
background=bg_color,
|
background=bg_color,
|
||||||
width=300,
|
width=300,
|
||||||
height=300)
|
height=300)
|
||||||
@@ -206,22 +211,22 @@ class web2pyDialog(object):
|
|||||||
|
|
||||||
logo = os.path.join('extras', 'icons', 'splashlogo.gif')
|
logo = os.path.join('extras', 'icons', 'splashlogo.gif')
|
||||||
if os.path.exists(logo):
|
if os.path.exists(logo):
|
||||||
img = Tkinter.PhotoImage(file=logo)
|
img = tkinter.PhotoImage(file=logo)
|
||||||
pnl = Tkinter.Label(self.logoarea, image=img, background=bg_color, bd=0)
|
pnl = tkinter.Label(self.logoarea, image=img, background=bg_color, bd=0)
|
||||||
pnl.pack(side='top', fill='both', expand='yes')
|
pnl.pack(side='top', fill='both', expand='yes')
|
||||||
# Prevent garbage collection of img
|
# Prevent garbage collection of img
|
||||||
pnl.image = img
|
pnl.image = img
|
||||||
|
|
||||||
# Prepare the banner area
|
# Prepare the banner area
|
||||||
self.bannerarea = Tkinter.Canvas(self.root,
|
self.bannerarea = tkinter.Canvas(self.root,
|
||||||
bg=bg_color,
|
bg=bg_color,
|
||||||
width=300,
|
width=300,
|
||||||
height=300)
|
height=300)
|
||||||
self.bannerarea.grid(row=1, column=1, columnspan=2, sticky=sticky)
|
self.bannerarea.grid(row=1, column=1, columnspan=2, sticky=sticky)
|
||||||
|
|
||||||
Tkinter.Label(self.bannerarea, anchor=Tkinter.N,
|
tkinter.Label(self.bannerarea, anchor=tkinter.N,
|
||||||
text=str(ProgramVersion + "\n" + ProgramAuthor),
|
text=str(ProgramVersion + "\n" + ProgramAuthor),
|
||||||
font=('Helvetica', 11), justify=Tkinter.CENTER,
|
font=('Helvetica', 11), justify=tkinter.CENTER,
|
||||||
foreground='#195866', background=bg_color,
|
foreground='#195866', background=bg_color,
|
||||||
height=3).pack(side='top',
|
height=3).pack(side='top',
|
||||||
fill='both',
|
fill='both',
|
||||||
@@ -230,24 +235,24 @@ class web2pyDialog(object):
|
|||||||
self.bannerarea.after(1000, self.update_canvas)
|
self.bannerarea.after(1000, self.update_canvas)
|
||||||
|
|
||||||
# IP
|
# IP
|
||||||
Tkinter.Label(self.root,
|
tkinter.Label(self.root,
|
||||||
text='Server IP:', bg=bg_color,
|
text='Server IP:', bg=bg_color,
|
||||||
justify=Tkinter.RIGHT).grid(row=4,
|
justify=tkinter.RIGHT).grid(row=4,
|
||||||
column=1,
|
column=1,
|
||||||
sticky=sticky)
|
sticky=sticky)
|
||||||
self.ips = {}
|
self.ips = {}
|
||||||
self.selected_ip = Tkinter.StringVar()
|
self.selected_ip = tkinter.StringVar()
|
||||||
row = 4
|
row = 4
|
||||||
ips = [('127.0.0.1', 'Local (IPv4)')] + \
|
ips = [('127.0.0.1', 'Local (IPv4)')] + \
|
||||||
([('::1', 'Local (IPv6)')] if socket.has_ipv6 else []) + \
|
([('::1', 'Local (IPv6)')] if socket.has_ipv6 else []) + \
|
||||||
[(ip, 'Public') for ip in options.ips] + \
|
[(ip, 'Public') for ip in options.ips] + \
|
||||||
[('0.0.0.0', 'Public')]
|
[('0.0.0.0', 'Public')]
|
||||||
for ip, legend in ips:
|
for ip, legend in ips:
|
||||||
self.ips[ip] = Tkinter.Radiobutton(
|
self.ips[ip] = tkinter.Radiobutton(
|
||||||
self.root, bg=bg_color, highlightthickness=0,
|
self.root, bg=bg_color, highlightthickness=0,
|
||||||
selectcolor='light grey', width=30,
|
selectcolor='light grey', width=30,
|
||||||
anchor=Tkinter.W, text='%s (%s)' % (legend, ip),
|
anchor=tkinter.W, text='%s (%s)' % (legend, ip),
|
||||||
justify=Tkinter.LEFT,
|
justify=tkinter.LEFT,
|
||||||
variable=self.selected_ip, value=ip)
|
variable=self.selected_ip, value=ip)
|
||||||
self.ips[ip].grid(row=row, column=2, sticky=sticky)
|
self.ips[ip].grid(row=row, column=2, sticky=sticky)
|
||||||
if row == 4:
|
if row == 4:
|
||||||
@@ -256,30 +261,30 @@ class web2pyDialog(object):
|
|||||||
shift = row
|
shift = row
|
||||||
|
|
||||||
# Port
|
# Port
|
||||||
Tkinter.Label(self.root,
|
tkinter.Label(self.root,
|
||||||
text='Server Port:', bg=bg_color,
|
text='Server Port:', bg=bg_color,
|
||||||
justify=Tkinter.RIGHT).grid(row=shift,
|
justify=tkinter.RIGHT).grid(row=shift,
|
||||||
column=1, pady=10,
|
column=1, pady=10,
|
||||||
sticky=sticky)
|
sticky=sticky)
|
||||||
|
|
||||||
self.port_number = Tkinter.Entry(self.root)
|
self.port_number = tkinter.Entry(self.root)
|
||||||
self.port_number.insert(Tkinter.END, self.options.port)
|
self.port_number.insert(tkinter.END, self.options.port)
|
||||||
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
|
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
|
||||||
|
|
||||||
# Password
|
# Password
|
||||||
Tkinter.Label(self.root,
|
tkinter.Label(self.root,
|
||||||
text='Choose Password:', bg=bg_color,
|
text='Choose Password:', bg=bg_color,
|
||||||
justify=Tkinter.RIGHT).grid(row=shift + 1,
|
justify=tkinter.RIGHT).grid(row=shift + 1,
|
||||||
column=1,
|
column=1,
|
||||||
sticky=sticky)
|
sticky=sticky)
|
||||||
|
|
||||||
self.password = Tkinter.Entry(self.root, show='*')
|
self.password = tkinter.Entry(self.root, show='*')
|
||||||
self.password.bind('<Return>', lambda e: self.start())
|
self.password.bind('<Return>', lambda e: self.start())
|
||||||
self.password.focus_force()
|
self.password.focus_force()
|
||||||
self.password.grid(row=shift + 1, column=2, sticky=sticky)
|
self.password.grid(row=shift + 1, column=2, sticky=sticky)
|
||||||
|
|
||||||
# Prepare the canvas
|
# Prepare the canvas
|
||||||
self.canvas = Tkinter.Canvas(self.root,
|
self.canvas = tkinter.Canvas(self.root,
|
||||||
width=400,
|
width=400,
|
||||||
height=100,
|
height=100,
|
||||||
bg='black')
|
bg='black')
|
||||||
@@ -288,19 +293,19 @@ class web2pyDialog(object):
|
|||||||
self.canvas.after(1000, self.update_canvas)
|
self.canvas.after(1000, self.update_canvas)
|
||||||
|
|
||||||
# Prepare the frame
|
# Prepare the frame
|
||||||
frame = Tkinter.Frame(self.root)
|
frame = tkinter.Frame(self.root)
|
||||||
frame.grid(row=shift + 3, column=1, columnspan=2, pady=5,
|
frame.grid(row=shift + 3, column=1, columnspan=2, pady=5,
|
||||||
sticky=sticky)
|
sticky=sticky)
|
||||||
|
|
||||||
# Start button
|
# Start button
|
||||||
self.button_start = Tkinter.Button(frame,
|
self.button_start = tkinter.Button(frame,
|
||||||
text='start server',
|
text='start server',
|
||||||
command=self.start)
|
command=self.start)
|
||||||
|
|
||||||
self.button_start.grid(row=0, column=0, sticky=sticky)
|
self.button_start.grid(row=0, column=0, sticky=sticky)
|
||||||
|
|
||||||
# Stop button
|
# Stop button
|
||||||
self.button_stop = Tkinter.Button(frame,
|
self.button_stop = tkinter.Button(frame,
|
||||||
text='stop server',
|
text='stop server',
|
||||||
command=self.stop)
|
command=self.stop)
|
||||||
|
|
||||||
@@ -454,9 +459,12 @@ class web2pyDialog(object):
|
|||||||
|
|
||||||
def error(self, message):
|
def error(self, message):
|
||||||
""" Shows error message """
|
""" Shows error message """
|
||||||
|
if PY2:
|
||||||
import tkMessageBox
|
import tkMessageBox as messagebox
|
||||||
tkMessageBox.showerror('web2py start server', message)
|
else:
|
||||||
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
messagebox.showerror('web2py start server', message)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
""" Starts web2py server """
|
""" Starts web2py server """
|
||||||
@@ -1191,10 +1199,13 @@ def start(cron=True):
|
|||||||
|
|
||||||
if not options.nogui and options.password == '<ask>':
|
if not options.nogui and options.password == '<ask>':
|
||||||
try:
|
try:
|
||||||
import Tkinter
|
if PY2:
|
||||||
|
import Tkinter as tkinter
|
||||||
|
else:
|
||||||
|
import tkinter
|
||||||
havetk = True
|
havetk = True
|
||||||
try:
|
try:
|
||||||
root = Tkinter.Tk()
|
root = tkinter.Tk()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
except (ImportError, OSError):
|
except (ImportError, OSError):
|
||||||
|
|||||||
+2
-1
@@ -15,6 +15,7 @@ except ImportError:
|
|||||||
import readline
|
import readline
|
||||||
try:
|
try:
|
||||||
from gluon import DAL
|
from gluon import DAL
|
||||||
|
from gluon.fileutils import open_file
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
print('gluon path not found')
|
print('gluon path not found')
|
||||||
|
|
||||||
@@ -531,7 +532,7 @@ class setCopyDB():
|
|||||||
self.db.export_to_csv_file(open('tmp.sql', 'wb'))
|
self.db.export_to_csv_file(open('tmp.sql', 'wb'))
|
||||||
|
|
||||||
print 'importing data...'
|
print 'importing data...'
|
||||||
other_db.import_from_csv_file(open('tmp.sql', 'rb'))
|
other_db.import_from_csv_file(open_file('tmp.sql', 'rb'))
|
||||||
other_db.commit()
|
other_db.commit()
|
||||||
print 'done!'
|
print 'done!'
|
||||||
print 'Attention: do not run this program again or you end up with duplicate records'
|
print 'Attention: do not run this program again or you end up with duplicate records'
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ legacy_db(legacy_db.mytable.id>0).select()
|
|||||||
If the script crashes this is might be due to that fact that the data_type_map dictionary below is incomplete.
|
If the script crashes this is might be due to that fact that the data_type_map dictionary below is incomplete.
|
||||||
Please complete it, improve it and continue.
|
Please complete it, improve it and continue.
|
||||||
|
|
||||||
Created by Falko Krause, minor modifications by Massimo Di Pierro and Ron McOuat
|
Created by Falko Krause, minor modifications by Massimo Di Pierro, Ron McOuat and Marvi Benedet
|
||||||
'''
|
'''
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
@@ -52,10 +52,11 @@ data_type_map = dict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def mysql(database_name, username, password):
|
def mysql(database_name, username, password, host):
|
||||||
p = subprocess.Popen(['mysql',
|
p = subprocess.Popen(['mysql',
|
||||||
'--user=%s' % username,
|
'--user=%s' % username,
|
||||||
'--password=%s' % password,
|
'--password=%s' % password,
|
||||||
|
'--host=%s' % host,
|
||||||
'--execute=show tables;',
|
'--execute=show tables;',
|
||||||
database_name],
|
database_name],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
@@ -64,14 +65,15 @@ def mysql(database_name, username, password):
|
|||||||
sql_showtables, stderr = p.communicate()
|
sql_showtables, stderr = p.communicate()
|
||||||
tables = [re.sub(
|
tables = [re.sub(
|
||||||
'\|\s+([^\|*])\s+.*', '\1', x) for x in sql_showtables.split()[1:]]
|
'\|\s+([^\|*])\s+.*', '\1', x) for x in sql_showtables.split()[1:]]
|
||||||
connection_string = "legacy_db = DAL('mysql://%s:%s@localhost/%s')" % (
|
connection_string = "legacy_db = DAL('mysql://%s:%s@%s/%s')" % (
|
||||||
username, password, database_name)
|
username, password, host, database_name)
|
||||||
legacy_db_table_web2py_code = []
|
legacy_db_table_web2py_code = []
|
||||||
for table_name in tables:
|
for table_name in tables:
|
||||||
#get the sql create statement
|
#get the sql create statement
|
||||||
p = subprocess.Popen(['mysqldump',
|
p = subprocess.Popen(['mysqldump',
|
||||||
'--user=%s' % username,
|
'--user=%s' % username,
|
||||||
'--password=%s' % password,
|
'--password=%s' % password,
|
||||||
|
'--host=%s' % host,
|
||||||
'--skip-add-drop-table',
|
'--skip-add-drop-table',
|
||||||
'--no-data', database_name,
|
'--no-data', database_name,
|
||||||
table_name], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
table_name], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
@@ -106,9 +108,13 @@ def mysql(database_name, username, password):
|
|||||||
legacy_db_table_web2py_code)
|
legacy_db_table_web2py_code)
|
||||||
return legacy_db_web2py_code
|
return legacy_db_web2py_code
|
||||||
|
|
||||||
regex = re.compile('(.*?):(.*?)@(.*)')
|
regex = re.compile('(.*):(.*)@((.*)/)?(.*)')
|
||||||
if len(sys.argv) < 2 or not regex.match(sys.argv[1]):
|
if len(sys.argv) < 2 or not regex.match(sys.argv[1]):
|
||||||
print 'USAGE:\n\n extract_mysql_models.py username:password@data_basename\n\n'
|
print 'USAGE:\n\n extract_mysql_models.py username:password@[host/]data_basename\n\n'
|
||||||
else:
|
else:
|
||||||
m = regex.match(sys.argv[1])
|
m = regex.match(sys.argv[1])
|
||||||
print mysql(m.group(3), m.group(1), m.group(2))
|
username = m.group(1)
|
||||||
|
password = m.group(2)
|
||||||
|
host = m.group(4) or 'localhost'
|
||||||
|
db_name = m.group(5)
|
||||||
|
print mysql(database_name = db_name, username = username, password = password, host = host)
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ fi
|
|||||||
|
|
||||||
/etc/init.d/nginx start
|
/etc/init.d/nginx start
|
||||||
systemctl start emperor.uwsgi.service
|
systemctl start emperor.uwsgi.service
|
||||||
|
systemctl enable emperor.uwsgi.service
|
||||||
|
|
||||||
echo <<EOF
|
echo <<EOF
|
||||||
you can stop uwsgi and nginx with
|
you can stop uwsgi and nginx with
|
||||||
|
|||||||
Reference in New Issue
Block a user