Compare commits
176 Commits
R-2.12.2
...
experimental
| Author | SHA1 | Date | |
|---|---|---|---|
| ba9e95a5c2 | |||
| 157146b948 | |||
| 82e4b7030c | |||
| 71fba07e3a | |||
| 2a7a4a3d04 | |||
| 999f235b75 | |||
| da22554aed | |||
| 0409d6f725 | |||
| b6235249da | |||
| fabadcd21f | |||
| 8e4ea3497b | |||
| 4b0e1856b5 | |||
| 94841c90c3 | |||
| f14e5f728c | |||
| 8443c17839 | |||
| be8114127e | |||
| 4eaef303ff | |||
| 319a3fc1dc | |||
| 463d643e2c | |||
| 0cbed12952 | |||
| b5994e57a4 | |||
| e239b975be | |||
| 0259ea3d29 | |||
| db4c008de3 | |||
| 7921e5148a | |||
| ee23eab77a | |||
| 2344386f77 | |||
| b5e12031c5 | |||
| 85e6840cf0 | |||
| 4c3006acb4 | |||
| f8f008cab5 | |||
| 6bff8af458 | |||
| b67edb083e | |||
| 4125a97ce1 | |||
| 78cf55bf9a | |||
| 931daaff89 | |||
| c6550f0adc | |||
| 22c89d8dcc | |||
| 1636528a0f | |||
| c02229d79c | |||
| acb05dbfe1 | |||
| fda1117dd7 | |||
| d1fde23182 | |||
| adf4c93860 | |||
| 02f1903c3d | |||
| 1137027ecc | |||
| 229616b9fc | |||
| a75a8cbf46 | |||
| 9650ff7516 | |||
| 5b90f3f532 | |||
| 483092787b | |||
| 9b17048882 | |||
| 30fe7400f9 | |||
| ada9353a7e | |||
| b0373297e0 | |||
| 2dbbef724c | |||
| eb7017fd9a | |||
| 4c039574df | |||
| ab900957fe | |||
| f960c8f6df | |||
| d2910327c0 | |||
| dfd6d52192 | |||
| 7dd8a3c853 | |||
| 71ae754fcd | |||
| 0520770a7e | |||
| 6b880fb455 | |||
| dd180019a1 | |||
| 638f1f902a | |||
| 73061e3bf5 | |||
| 5a18e29c2e | |||
| 721af77c90 | |||
| 2cf6797b43 | |||
| 5c4145743f | |||
| b4733e4617 | |||
| b51d217d9b | |||
| 864dbe73f2 | |||
| b942fc8f7a | |||
| b2a65dbba4 | |||
| d36d4d77f7 | |||
| c8db6d5fb7 | |||
| 1b77c2294a | |||
| 98a81c9fbd | |||
| 4bf5a70dc0 | |||
| d883e3d84e | |||
| db37cf6a58 | |||
| dba5c97d51 | |||
| 948bd0c671 | |||
| 5d8ff8ba2c | |||
| 503cd59adc | |||
| a0bcd2287b | |||
| 430163f70b | |||
| 52b59e9b71 | |||
| e8f87ea274 | |||
| fb6fa0c448 | |||
| 935c95ccfc | |||
| e180e69467 | |||
| 5c9d197f93 | |||
| e417d311e5 | |||
| 199f93f262 | |||
| 64a8880c80 | |||
| 257c514bd4 | |||
| 12f848c899 | |||
| 4de007a946 | |||
| b59a93e24e | |||
| bbed326c20 | |||
| 874398c38c | |||
| a9f8fbadae | |||
| e62320ff9f | |||
| b3e606295e | |||
| b8c2bd7303 | |||
| 1387b26606 | |||
| c6a7732d32 | |||
| 0036d9c45b | |||
| b99fb7dedf | |||
| 344590470b | |||
| 2c57dc084e | |||
| c17ba0a020 | |||
| 7d4b460e1b | |||
| 6680ea8ab7 | |||
| 353db90a64 | |||
| 827e663ac4 | |||
| de399691ce | |||
| 46f081c45c | |||
| 0fa0dbaeea | |||
| b47511c896 | |||
| e31318eaa8 | |||
| 72ee538883 | |||
| b6ddc6098e | |||
| 90854eae44 | |||
| 2bceb3f95f | |||
| 9da1e29014 | |||
| 39ba9dc1a9 | |||
| 36db9719ef | |||
| 125cbd93a0 | |||
| bc267ce17b | |||
| 65c87386c1 | |||
| 2a245d36f4 | |||
| dcf64a661d | |||
| 1c74afc01b | |||
| 5dbcda9f38 | |||
| ac02d52f05 | |||
| d4270373e1 | |||
| e4b27080ca | |||
| 692791a518 | |||
| 9190191c7a | |||
| 7bd8f6a1a9 | |||
| 64e115f442 | |||
| 61f685d225 | |||
| c56fc2f6a0 | |||
| bb2aa29867 | |||
| 08b6832809 | |||
| cbbd1246db | |||
| 0a79bf3afd | |||
| db5e58e49f | |||
| 5030d3144f | |||
| edcc2e44dc | |||
| 2cf9f26b0d | |||
| 4b99b6fdd7 | |||
| 622430583f | |||
| 89cc5a5f70 | |||
| 6899154fcd | |||
| 47c0e461f1 | |||
| 93237837ed | |||
| cf20ce5fae | |||
| 04c86f07ef | |||
| 1a12c4011b | |||
| 85bbe15758 | |||
| 5816481a44 | |||
| 2675e9d229 | |||
| 41498917d5 | |||
| 8f7acd8154 | |||
| 26865421b6 | |||
| f94bc250eb | |||
| 8078d4b0f3 | |||
| 5ee8c9c930 | |||
| 6659bc0793 |
@@ -59,3 +59,4 @@ HOWTO-web2py-devel
|
|||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.idea/*
|
.idea/*
|
||||||
site-packages/
|
site-packages/
|
||||||
|
logs/
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ before_script:
|
|||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi
|
||||||
|
- mysql -e 'create database pydal;'
|
||||||
|
- psql -c 'create database pydal;' -U postgres
|
||||||
|
- psql -c 'create extension postgis;' -U postgres -d pydal;
|
||||||
|
- psql -c 'SHOW SERVER_VERSION' -U postgres
|
||||||
|
|
||||||
|
|
||||||
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
|
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
|
||||||
@@ -28,3 +32,6 @@ after_success:
|
|||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: true
|
email: true
|
||||||
|
|
||||||
|
addons:
|
||||||
|
postgresql: "9.4"
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
## 2.12.1
|
## 2.13.1-2
|
||||||
|
|
||||||
|
- fixed a security issue in request_reset_password
|
||||||
|
- added fabfile.py
|
||||||
|
- fixed oauth2 renew token, thanks dokime7
|
||||||
|
- fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning)
|
||||||
|
- allow passing unicode to template render
|
||||||
|
- allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg
|
||||||
|
- allow HttpOnly cookies
|
||||||
|
- french pluralizaiton rules, thanks Mathieu Clabaut
|
||||||
|
- fixed bug in redirect to cas service, thanks Fernando González
|
||||||
|
- allow deploying to pythonanywhere from the web2py admin that you're running locally, thanks Leonel
|
||||||
|
- better tests
|
||||||
|
- many more bug fixes
|
||||||
|
|
||||||
|
## 2.12.1-3
|
||||||
|
|
||||||
- security fix: Validate for open redirect everywhere, not just in login()
|
- security fix: Validate for open redirect everywhere, not just in login()
|
||||||
- allow to pack invidual apps and selected files as packed exe files
|
- allow to pack invidual apps and selected files as packed exe files
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ clean:
|
|||||||
find ./ -name '*.rej' -exec rm -f {} \;
|
find ./ -name '*.rej' -exec rm -f {} \;
|
||||||
find ./ -name '#*' -exec rm -f {} \;
|
find ./ -name '#*' -exec rm -f {} \;
|
||||||
find ./ -name 'Thumbs.db' -exec rm -f {} \;
|
find ./ -name 'Thumbs.db' -exec rm -f {} \;
|
||||||
find ./gluon/ -name '.*' -exec rm -f {} \;
|
# find ./gluon/ -name '.*' -exec rm -f {} \;
|
||||||
find ./gluon/ -name '*class' -exec rm -f {} \;
|
find ./gluon/ -name '*class' -exec rm -f {} \;
|
||||||
find ./applications/admin/ -name '.*' -exec rm -f {} \;
|
find ./applications/admin/ -name '.*' -exec rm -f {} \;
|
||||||
find ./applications/examples/ -name '.*' -exec rm -f {} \;
|
find ./applications/examples/ -name '.*' -exec rm -f {} \;
|
||||||
@@ -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.12.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
echo 'Version 2.13.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||||
### rm -f all junk files
|
### rm -f all junk files
|
||||||
make clean
|
make clean
|
||||||
### clean up baisc apps
|
### clean up baisc apps
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.12.2-stable+timestamp.2015.08.09.09.27.09
|
Version 2.13.3-stable+timestamp.2015.12.24.08.08.22
|
||||||
|
|||||||
@@ -445,30 +445,31 @@ def ccache():
|
|||||||
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
|
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
|
||||||
total.update(gae_stats)
|
total.update(gae_stats)
|
||||||
else:
|
else:
|
||||||
|
# get ram stats directly from the cache object
|
||||||
|
ram_stats = cache.ram.stats[request.application]
|
||||||
|
ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
|
||||||
|
ram['misses'] = ram_stats['misses']
|
||||||
|
try:
|
||||||
|
ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
|
||||||
|
except (KeyError, ZeroDivisionError):
|
||||||
|
ram['ratio'] = 0
|
||||||
|
|
||||||
for key, value in cache.ram.storage.iteritems():
|
for key, value in cache.ram.storage.iteritems():
|
||||||
if isinstance(value, dict):
|
if hp:
|
||||||
ram['hits'] = value['hit_total'] - value['misses']
|
ram['bytes'] += hp.iso(value[1]).size
|
||||||
ram['misses'] = value['misses']
|
ram['objects'] += hp.iso(value[1]).count
|
||||||
try:
|
ram['entries'] += 1
|
||||||
ram['ratio'] = ram['hits'] * 100 / value['hit_total']
|
if value[0] < ram['oldest']:
|
||||||
except (KeyError, ZeroDivisionError):
|
ram['oldest'] = value[0]
|
||||||
ram['ratio'] = 0
|
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
||||||
else:
|
|
||||||
if hp:
|
|
||||||
ram['bytes'] += hp.iso(value[1]).size
|
|
||||||
ram['objects'] += hp.iso(value[1]).count
|
|
||||||
ram['entries'] += 1
|
|
||||||
if value[0] < ram['oldest']:
|
|
||||||
ram['oldest'] = value[0]
|
|
||||||
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
|
||||||
|
|
||||||
for key in cache.disk.storage:
|
for key in cache.disk.storage:
|
||||||
value = cache.disk.storage[key]
|
value = cache.disk.storage[key]
|
||||||
if isinstance(value, dict):
|
if isinstance(value[1], dict):
|
||||||
disk['hits'] = value['hit_total'] - value['misses']
|
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
||||||
disk['misses'] = value['misses']
|
disk['misses'] = value[1]['misses']
|
||||||
try:
|
try:
|
||||||
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
|
disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
|
||||||
except (KeyError, ZeroDivisionError):
|
except (KeyError, ZeroDivisionError):
|
||||||
disk['ratio'] = 0
|
disk['ratio'] = 0
|
||||||
else:
|
else:
|
||||||
@@ -485,7 +486,7 @@ def ccache():
|
|||||||
ram_keys.remove('oldest')
|
ram_keys.remove('oldest')
|
||||||
for key in ram_keys:
|
for key in ram_keys:
|
||||||
total[key] = ram[key] + disk[key]
|
total[key] = ram[key] + disk[key]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
total['ratio'] = total['hits'] * 100 / (total['hits'] +
|
total['ratio'] = total['hits'] * 100 / (total['hits'] +
|
||||||
total['misses'])
|
total['misses'])
|
||||||
@@ -575,7 +576,7 @@ def bg_graph_model():
|
|||||||
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
||||||
|
|
||||||
group = meta_graphmodel['group'].replace(' ', '')
|
group = meta_graphmodel['group'].replace(' ', '')
|
||||||
if not subgraphs.has_key(group):
|
if group not in subgraphs:
|
||||||
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
||||||
subgraphs[group]['tables'].append(tablename)
|
subgraphs[group]['tables'].append(tablename)
|
||||||
|
|
||||||
|
|||||||
@@ -484,9 +484,15 @@ def cleanup():
|
|||||||
|
|
||||||
def compile_app():
|
def compile_app():
|
||||||
app = get_app()
|
app = get_app()
|
||||||
c = app_compile(app, request)
|
c = app_compile(app, request,
|
||||||
|
skip_failed_views = (request.args(1) == 'skip_failed_views'))
|
||||||
if not c:
|
if not c:
|
||||||
session.flash = T('application compiled')
|
session.flash = T('application compiled')
|
||||||
|
elif isinstance(c, list):
|
||||||
|
session.flash = DIV(*[T('application compiled'), BR(), BR(),
|
||||||
|
T('WARNING: The following views could not be compiled:'), BR()] +
|
||||||
|
[CAT(BR(), view) for view in c] +
|
||||||
|
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
|
||||||
else:
|
else:
|
||||||
session.flash = DIV(T('Cannot compile: there are errors in your app:'),
|
session.flash = DIV(T('Cannot compile: there are errors in your app:'),
|
||||||
CODE(c))
|
CODE(c))
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import gzip
|
||||||
|
import tarfile
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
from xmlrpclib import ProtocolError
|
||||||
|
from gluon.contrib.simplejsonrpc import ServerProxy
|
||||||
|
|
||||||
|
|
||||||
|
def deploy():
|
||||||
|
response.title = T('Deploy to pythonanywhere')
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def create_account():
|
||||||
|
""" Create a PythonAnywhere account """
|
||||||
|
if not request.vars:
|
||||||
|
raise HTTP(400)
|
||||||
|
|
||||||
|
if request.vars.username and request.vars.web2py_admin_password:
|
||||||
|
# Check if web2py is already there otherwise we get an error 500 too.
|
||||||
|
client = ServerProxy('https://%(username)s:%(web2py_admin_password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
|
||||||
|
try:
|
||||||
|
if client.login() is True:
|
||||||
|
return response.json({'status': 'ok'})
|
||||||
|
except ProtocolError as error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import urllib, urllib2
|
||||||
|
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
|
||||||
|
data = urllib.urlencode(request.vars)
|
||||||
|
req = urllib2.Request(url, data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
reply = urllib2.urlopen(req)
|
||||||
|
except urllib2.HTTPError as error:
|
||||||
|
if error.code == 400:
|
||||||
|
reply = error
|
||||||
|
elif error.code == 500:
|
||||||
|
return response.json({'status':'error', 'errors':{'username': ['An App other than web2py is installed in the domain %(username)s.pythonanywhere.com' % request.vars]}})
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
response.headers['Content-Type'] = 'application/json'
|
||||||
|
return reply.read()
|
||||||
|
|
||||||
|
|
||||||
|
def list_apps():
|
||||||
|
""" Get a list of apps both remote and local """
|
||||||
|
if not request.vars.username or not request.vars.password:
|
||||||
|
raise HTTP(400)
|
||||||
|
client = ServerProxy('https://%(username)s:%(password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
|
||||||
|
regex = re.compile('^\w+$')
|
||||||
|
local = [f for f in os.listdir(apath(r=request)) if regex.match(f)]
|
||||||
|
try:
|
||||||
|
pythonanywhere = client.list_apps()
|
||||||
|
except ProtocolError as error:
|
||||||
|
raise HTTP(error.errcode)
|
||||||
|
return response.json({'local': local, 'pythonanywhere': pythonanywhere})
|
||||||
|
|
||||||
|
|
||||||
|
def bulk_install():
|
||||||
|
""" Install a list of apps """
|
||||||
|
|
||||||
|
def b64pack(app):
|
||||||
|
"""
|
||||||
|
Given an app's name, return the base64 representation of its packed version.
|
||||||
|
"""
|
||||||
|
folder = apath(app, r=request)
|
||||||
|
tmpfile = StringIO()
|
||||||
|
tar = tarfile.TarFile(fileobj=tmpfile, mode='w')
|
||||||
|
try:
|
||||||
|
filenames = listdir(folder, '^[\w\.\-]+$', add_dirs=True,
|
||||||
|
exclude_content_from=['cache', 'sessions', 'errors'])
|
||||||
|
for fname in filenames:
|
||||||
|
tar.add(os.path.join(folder, fname), fname, False)
|
||||||
|
finally:
|
||||||
|
tar.close()
|
||||||
|
tmpfile.seek(0)
|
||||||
|
gzfile = StringIO()
|
||||||
|
w2pfp = gzip.GzipFile(fileobj=gzfile, mode='wb')
|
||||||
|
w2pfp.write(tmpfile.read())
|
||||||
|
w2pfp.close()
|
||||||
|
gzfile.seek(0)
|
||||||
|
return base64.b64encode(gzfile.read())
|
||||||
|
|
||||||
|
request.vars.apps = request.vars['apps[]']
|
||||||
|
if not request.vars.apps or not request.vars.username or not request.vars.password:
|
||||||
|
raise HTTP(400)
|
||||||
|
if not isinstance(request.vars.apps, list):
|
||||||
|
request.vars.apps = [request.vars.apps] # Only one app selected
|
||||||
|
|
||||||
|
client = ServerProxy('https://%(username)s:%(password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
|
||||||
|
|
||||||
|
for app in request.vars.apps:
|
||||||
|
try:
|
||||||
|
client.install(app, app+'.w2p', b64pack(app))
|
||||||
|
except ProtocolError as error:
|
||||||
|
raise HTTP(error.errcode)
|
||||||
|
|
||||||
|
return response.json({'status': 'ok'})
|
||||||
+408
-377
@@ -1,377 +1,408 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
{
|
{
|
||||||
'!langcode!': 'pt',
|
'!langcode!': 'pt',
|
||||||
'!langname!': 'Português',
|
'!langname!': 'Português',
|
||||||
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novo_valor\'". Não é permitido atualizar ou apagar resultados de um JOIN',
|
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novo_valor\'". Não é permitido atualizar ou apagar resultados de um JOIN',
|
||||||
'%s %%{row} deleted': '%s registros apagados',
|
'%s %%{row} deleted': '%s registros apagados',
|
||||||
'%s %%{row} updated': '%s registros atualizados',
|
'%s %%{row} updated': '%s registros atualizados',
|
||||||
'%Y-%m-%d': '%d/%m/%Y',
|
'%Y-%m-%d': '%d/%m/%Y',
|
||||||
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||||
'(requires internet access)': '(requer acesso à internet)',
|
'(requires internet access)': '(requer acesso à internet)',
|
||||||
'(requires internet access, experimental)': '(requer acesso à internet, experimental)',
|
'(requires internet access, experimental)': '(requer acesso à internet, experimental)',
|
||||||
'(something like "it-it")': '(algo como "it-it")',
|
'(something like "it-it")': '(algo como "it-it")',
|
||||||
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
|
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
|
||||||
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
|
||||||
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
|
||||||
'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
|
'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
|
||||||
'A new version of web2py is available: %s': 'Está disponível uma nova versão do web2py: %s',
|
'A new version of web2py is available: %s': 'Está disponível uma nova versão do web2py: %s',
|
||||||
'About': 'sobre',
|
'About': 'sobre',
|
||||||
'About application': 'Sobre a aplicação',
|
'About application': 'Sobre a aplicação',
|
||||||
'additional code for your application': 'código adicional para sua aplicação',
|
'Accept Terms': 'Accept Terms',
|
||||||
'Additional code for your application': 'Código adicional para a sua aplicação',
|
'additional code for your application': 'código adicional para sua aplicação',
|
||||||
'admin disabled because no admin password': ' admin desabilitado por falta de senha definida',
|
'Additional code for your application': 'Código adicional para a sua aplicação',
|
||||||
'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE',
|
'admin disabled because no admin password': ' admin desabilitado por falta de senha definida',
|
||||||
'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha',
|
'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE',
|
||||||
'Admin is disabled because insecure channel': 'Admin desabilitado pois o canal não é seguro',
|
'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha',
|
||||||
'Admin is disabled because unsecure channel': 'Admin desabilitado pois o canal não é seguro',
|
'Admin is disabled because insecure channel': 'Admin desabilitado pois o canal não é seguro',
|
||||||
'Admin language': 'Linguagem do Admin',
|
'Admin is disabled because unsecure channel': 'Admin desabilitado pois o canal não é seguro',
|
||||||
'administrative interface': 'interface administrativa',
|
'Admin language': 'Linguagem do Admin',
|
||||||
'Administrator Password:': 'Senha de administrador:',
|
'administrative interface': 'interface administrativa',
|
||||||
'and rename it (required):': 'e renomeie (requerido):',
|
'Administrator Password:': 'Senha de administrador:',
|
||||||
'and rename it:': ' e renomeie:',
|
'and rename it (required):': 'e renomeie (requerido):',
|
||||||
'appadmin': 'appadmin',
|
'and rename it:': ' e renomeie:',
|
||||||
'appadmin is disabled because insecure channel': 'admin desabilitado, canal inseguro',
|
'appadmin': 'appadmin',
|
||||||
'application "%s" uninstalled': 'aplicação "%s" desinstalada',
|
'appadmin is disabled because insecure channel': 'admin desabilitado, canal inseguro',
|
||||||
'application compiled': 'aplicação compilada',
|
'application "%s" uninstalled': 'aplicação "%s" desinstalada',
|
||||||
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
|
'application compiled': 'aplicação compilada',
|
||||||
'Application name:': 'Nome da aplicação:',
|
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
|
||||||
'are not used': 'não usadas',
|
'Application name:': 'Nome da aplicação:',
|
||||||
'are not used yet': 'ainda não usadas',
|
'are not used': 'não usadas',
|
||||||
'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?',
|
'are not used yet': 'ainda não usadas',
|
||||||
'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?',
|
'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?',
|
||||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?',
|
||||||
'Are you sure you want to uninstall application "%s"': 'Tem certeza que deseja apagar a aplicação "%s"?',
|
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||||
'Are you sure you want to uninstall application "%s"?': 'Tem certeza que deseja apagar a aplicação "%s"?',
|
'Are you sure you want to uninstall application "%s"': 'Tem certeza que deseja apagar a aplicação "%s"?',
|
||||||
'Are you sure you want to upgrade web2py now?': 'Tem certeza que deseja atualizar o web2py agora?',
|
'Are you sure you want to uninstall application "%s"?': 'Tem certeza que deseja apagar a aplicação "%s"?',
|
||||||
'arguments': 'argumentos',
|
'Are you sure you want to upgrade web2py now?': 'Tem certeza que deseja atualizar o web2py agora?',
|
||||||
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.',
|
'arguments': 'argumentos',
|
||||||
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.',
|
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.',
|
||||||
'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!',
|
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.',
|
||||||
'Autocomplete Python Code': 'Autocompletar Código Python',
|
'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!',
|
||||||
'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
|
'Autocomplete Python Code': 'Autocompletar Código Python',
|
||||||
'back': 'voltar',
|
'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
|
||||||
'browse': 'buscar',
|
'back': 'voltar',
|
||||||
'cache': 'cache',
|
'Begin': 'Begin',
|
||||||
'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas',
|
'browse': 'buscar',
|
||||||
'can be a git repo': 'can be a git repo',
|
'cache': 'cache',
|
||||||
'Cannot be empty': 'Não pode ser vazio',
|
'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas',
|
||||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente',
|
'can be a git repo': 'can be a git repo',
|
||||||
'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação',
|
'Cannot be empty': 'Não pode ser vazio',
|
||||||
'cannot create file': 'Não é possível criar o arquivo',
|
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente',
|
||||||
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
|
'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação',
|
||||||
'Change admin password': 'mudar senha de administrador',
|
'cannot create file': 'Não é possível criar o arquivo',
|
||||||
'change editor settings': 'mudar definições do editor',
|
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
|
||||||
'Change Password': 'Trocar Senha',
|
'Change admin password': 'mudar senha de administrador',
|
||||||
'check all': 'marcar todos',
|
'change editor settings': 'mudar definições do editor',
|
||||||
'Check for upgrades': 'checar por atualizações',
|
'Change Password': 'Trocar Senha',
|
||||||
'Check to delete': 'Marque para apagar',
|
'check all': 'marcar todos',
|
||||||
'Checking for upgrades...': 'Buscando atualizações...',
|
'Check for upgrades': 'checar por atualizações',
|
||||||
'Clean': 'limpar',
|
'Check to delete': 'Marque para apagar',
|
||||||
'click here for online examples': 'clique para ver exemplos online',
|
'Checking for upgrades...': 'Buscando atualizações...',
|
||||||
'click here for the administrative interface': 'Clique aqui para acessar a interface administrativa',
|
'Clean': 'limpar',
|
||||||
'Click row to expand traceback': 'Clique em uma coluna para expandir o log do erro',
|
'click here for online examples': 'clique para ver exemplos online',
|
||||||
'click to check for upgrades': 'clique aqui para checar por atualizações',
|
'click here for the administrative interface': 'Clique aqui para acessar a interface administrativa',
|
||||||
'click to open': 'clique para abrir',
|
'Click row to expand traceback': 'Clique em uma coluna para expandir o log do erro',
|
||||||
'Client IP': 'IP do cliente',
|
'click to check for upgrades': 'clique aqui para checar por atualizações',
|
||||||
'code': 'código',
|
'click to open': 'clique para abrir',
|
||||||
'collapse/expand all': 'colapsar/expandir tudo',
|
'Client IP': 'IP do cliente',
|
||||||
'commit (mercurial)': 'commit (mercurial)',
|
'code': 'código',
|
||||||
'Compile': 'compilar',
|
'collapse/expand all': 'colapsar/expandir tudo',
|
||||||
'compiled application removed': 'aplicação compilada removida',
|
'commit (mercurial)': 'commit (mercurial)',
|
||||||
'Controllers': 'Controladores',
|
'Compile': 'compilar',
|
||||||
'controllers': 'controladores',
|
'compiled application removed': 'aplicação compilada removida',
|
||||||
'Count': 'Contagem',
|
'Controllers': 'Controladores',
|
||||||
'Create': 'criar',
|
'controllers': 'controladores',
|
||||||
'create file with filename:': 'criar um arquivo com o nome:',
|
'Count': 'Contagem',
|
||||||
'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente',
|
'Create': 'criar',
|
||||||
'create new application:': 'nome da nova aplicação:',
|
'create file with filename:': 'criar um arquivo com o nome:',
|
||||||
'Create new simple application': 'Crie uma nova aplicação',
|
'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente',
|
||||||
'Create/Upload': 'Create/Upload',
|
'create new application:': 'nome da nova aplicação:',
|
||||||
'created by': 'criado por',
|
'Create new simple application': 'Crie uma nova aplicação',
|
||||||
'crontab': 'crontab',
|
'Create/Upload': 'Create/Upload',
|
||||||
'Current request': 'Requisição atual',
|
'created by': 'criado por',
|
||||||
'Current response': 'Resposta atual',
|
'crontab': 'crontab',
|
||||||
'Current session': 'Sessão atual',
|
'Current request': 'Requisição atual',
|
||||||
'currently running': 'Executando',
|
'Current response': 'Resposta atual',
|
||||||
'currently saved or': 'Atualmente salvo ou',
|
'Current session': 'Sessão atual',
|
||||||
'customize me!': 'Modifique-me',
|
'currently running': 'Executando',
|
||||||
'data uploaded': 'Dados enviados',
|
'currently saved or': 'Atualmente salvo ou',
|
||||||
'database': 'banco de dados',
|
'customize me!': 'Modifique-me',
|
||||||
'database %s select': 'Seleção no banco de dados %s',
|
'data uploaded': 'Dados enviados',
|
||||||
'database administration': 'administração de banco de dados',
|
'database': 'banco de dados',
|
||||||
'Date and Time': 'Data e Hora',
|
'database %s select': 'Seleção no banco de dados %s',
|
||||||
'db': 'db',
|
'database administration': 'administração de banco de dados',
|
||||||
'Debug': 'Debug',
|
'Date and Time': 'Data e Hora',
|
||||||
'defines tables': 'define as tabelas',
|
'db': 'db',
|
||||||
'Delete': 'Apague',
|
'Debug': 'Debug',
|
||||||
'delete': 'apagar',
|
'defines tables': 'define as tabelas',
|
||||||
'delete all checked': 'apagar marcados',
|
'Delete': 'Apague',
|
||||||
'delete plugin': 'apagar plugin',
|
'delete': 'apagar',
|
||||||
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
|
'delete all checked': 'apagar marcados',
|
||||||
'Delete:': 'Apague:',
|
'delete plugin': 'apagar plugin',
|
||||||
'Deploy': 'publicar',
|
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
|
||||||
'Deploy on Google App Engine': 'Publicar no Google App Engine',
|
'Delete:': 'Apague:',
|
||||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
'Deploy': 'publicar',
|
||||||
'Description': 'Descrição',
|
'Deploy on Google App Engine': 'Publicar no Google App Engine',
|
||||||
'design': 'modificar',
|
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||||
'DESIGN': 'Projeto',
|
'Deploy to pythonanywhere': 'Deploy to pythonanywhere',
|
||||||
'Design for': 'Projeto de',
|
'Deploy to PythonAnywhere': 'Deploy to PythonAnywhere',
|
||||||
'Detailed traceback description': 'Detailed traceback description',
|
'Deployment Interface': 'Deployment Interface',
|
||||||
'direction: ltr': 'direção: ltr',
|
'Description': 'Descrição',
|
||||||
'Disable': 'Disable',
|
'design': 'modificar',
|
||||||
'docs': 'docs',
|
'DESIGN': 'Projeto',
|
||||||
'done!': 'feito!',
|
'Design for': 'Projeto de',
|
||||||
'download layouts': 'download layouts',
|
'Detailed traceback description': 'Detailed traceback description',
|
||||||
'Download layouts from repository': 'Download layouts from repository',
|
'details': 'details',
|
||||||
'download plugins': 'download plugins',
|
'direction: ltr': 'direção: ltr',
|
||||||
'Download plugins from repository': 'Download plugins from repository',
|
'Disable': 'Disable',
|
||||||
'E-mail': 'E-mail',
|
'docs': 'docs',
|
||||||
'EDIT': 'EDITAR',
|
'done!': 'feito!',
|
||||||
'Edit': 'editar',
|
'download layouts': 'download layouts',
|
||||||
'Edit application': 'Editar aplicação',
|
'Download layouts from repository': 'Download layouts from repository',
|
||||||
'edit controller': 'editar controlador',
|
'download plugins': 'download plugins',
|
||||||
'Edit current record': 'Editar o registro atual',
|
'Download plugins from repository': 'Download plugins from repository',
|
||||||
'Edit Profile': 'Editar Perfil',
|
'E-mail': 'E-mail',
|
||||||
'edit views:': 'editar visões:',
|
'EDIT': 'EDITAR',
|
||||||
'Editing %s': 'A Editar %s',
|
'Edit': 'editar',
|
||||||
'Editing file': 'Editando arquivo',
|
'Edit application': 'Editar aplicação',
|
||||||
'Editing file "%s"': 'Editando arquivo "%s"',
|
'edit controller': 'editar controlador',
|
||||||
'Editing Language file': 'Editando arquivo de linguagem',
|
'Edit current record': 'Editar o registro atual',
|
||||||
'Enterprise Web Framework': 'Framework web empresarial',
|
'Edit Profile': 'Editar Perfil',
|
||||||
'Error': 'Erro',
|
'edit views:': 'editar visões:',
|
||||||
'Error logs for "%(app)s"': 'Logs de erro para "%(app)s"',
|
'Editing %s': 'A Editar %s',
|
||||||
'Error snapshot': 'Error snapshot',
|
'Editing file': 'Editando arquivo',
|
||||||
'Error ticket': 'Error ticket',
|
'Editing file "%s"': 'Editando arquivo "%s"',
|
||||||
'Errors': 'erros',
|
'Editing Language file': 'Editando arquivo de linguagem',
|
||||||
'Exception instance attributes': 'Atributos da instancia de excessão',
|
'Email Address': 'Email Address',
|
||||||
'Exit Fullscreen': 'Sair de Ecrã Inteiro',
|
'Enterprise Web Framework': 'Framework web empresarial',
|
||||||
'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)',
|
'Error': 'Erro',
|
||||||
'export as csv file': 'exportar como arquivo CSV',
|
'Error logs for "%(app)s"': 'Logs de erro para "%(app)s"',
|
||||||
'exposes': 'expõe',
|
'Error snapshot': 'Error snapshot',
|
||||||
'extends': 'estende',
|
'Error ticket': 'Error ticket',
|
||||||
'failed to reload module': 'Falha ao recarregar o módulo',
|
'Errors': 'erros',
|
||||||
'failed to reload module because:': 'falha ao recarregar o módulo por:',
|
'Exception instance attributes': 'Atributos da instancia de excessão',
|
||||||
'File': 'Arquivo',
|
'Exit Fullscreen': 'Sair de Ecrã Inteiro',
|
||||||
'file "%(filename)s" created': 'arquivo "%(filename)s" criado',
|
'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)',
|
||||||
'file "%(filename)s" deleted': 'arquivo "%(filename)s" apagado',
|
'export as csv file': 'exportar como arquivo CSV',
|
||||||
'file "%(filename)s" uploaded': 'arquivo "%(filename)s" enviado',
|
'exposes': 'expõe',
|
||||||
'file "%(filename)s" was not deleted': 'arquivo "%(filename)s" não foi apagado',
|
'exposes:': 'exposes:',
|
||||||
'file "%s" of %s restored': 'arquivo "%s" de %s restaurado',
|
'extends': 'estende',
|
||||||
'file changed on disk': 'arquivo modificado no disco',
|
'failed to reload module': 'Falha ao recarregar o módulo',
|
||||||
'file does not exist': 'arquivo não existe',
|
'failed to reload module because:': 'falha ao recarregar o módulo por:',
|
||||||
'file saved on %(time)s': 'arquivo salvo em %(time)s',
|
'File': 'Arquivo',
|
||||||
'file saved on %s': 'arquivo salvo em %s',
|
'file "%(filename)s" created': 'arquivo "%(filename)s" criado',
|
||||||
'filter': 'filtro',
|
'file "%(filename)s" deleted': 'arquivo "%(filename)s" apagado',
|
||||||
'Find Next': 'Localizar Seguinte',
|
'file "%(filename)s" uploaded': 'arquivo "%(filename)s" enviado',
|
||||||
'Find Previous': 'Localizar Anterior',
|
'file "%(filename)s" was not deleted': 'arquivo "%(filename)s" não foi apagado',
|
||||||
'First name': 'Nome',
|
'file "%s" of %s restored': 'arquivo "%s" de %s restaurado',
|
||||||
'Frames': 'Frames',
|
'file changed on disk': 'arquivo modificado no disco',
|
||||||
'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].',
|
'file does not exist': 'arquivo não existe',
|
||||||
'graph model': 'graph model',
|
'file saved on %(time)s': 'arquivo salvo em %(time)s',
|
||||||
'Group ID': 'ID do Grupo',
|
'file saved on %s': 'arquivo salvo em %s',
|
||||||
'Hello World': 'Olá Mundo',
|
'filter': 'filtro',
|
||||||
'Help': 'ajuda',
|
'Find Next': 'Localizar Seguinte',
|
||||||
'Hide/Show Translated strings': '',
|
'Find Previous': 'Localizar Anterior',
|
||||||
'htmledit': 'htmledit',
|
'First name': 'Nome',
|
||||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.',
|
'Form has errors': 'Form has errors',
|
||||||
'Import/Export': 'Importar/Exportar',
|
'Frames': 'Frames',
|
||||||
'includes': 'inclui',
|
'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].',
|
||||||
'insert new': 'inserir novo',
|
'graph model': 'graph model',
|
||||||
'insert new %s': 'inserir novo %s',
|
'Group ID': 'ID do Grupo',
|
||||||
'inspect attributes': 'inspecionar atributos',
|
'Hello World': 'Olá Mundo',
|
||||||
'Install': 'instalar',
|
'Help': 'ajuda',
|
||||||
'Installed applications': 'Aplicações instaladas',
|
'Hide/Show Translated strings': '',
|
||||||
'internal error': 'erro interno',
|
'htmledit': 'htmledit',
|
||||||
'Internal State': 'Estado Interno',
|
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.',
|
||||||
'Invalid action': 'Ação inválida',
|
'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.',
|
||||||
'Invalid email': 'E-mail inválido',
|
'Import/Export': 'Importar/Exportar',
|
||||||
'invalid password': 'senha inválida',
|
'includes': 'inclui',
|
||||||
'Invalid Query': 'Consulta inválida',
|
'insert new': 'inserir novo',
|
||||||
'invalid request': 'solicitação inválida',
|
'insert new %s': 'inserir novo %s',
|
||||||
'invalid ticket': 'ticket inválido',
|
'inspect attributes': 'inspecionar atributos',
|
||||||
'Keyboard shortcuts': 'Atalhos de teclado',
|
'Install': 'instalar',
|
||||||
'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado',
|
'Installed applications': 'Aplicações instaladas',
|
||||||
'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados',
|
'internal error': 'erro interno',
|
||||||
'languages': 'linguagens',
|
'Internal State': 'Estado Interno',
|
||||||
'Languages': 'Linguagens',
|
'Invalid action': 'Ação inválida',
|
||||||
'languages updated': 'linguagens atualizadas',
|
'Invalid email': 'E-mail inválido',
|
||||||
'Last name': 'Sobrenome',
|
'invalid password': 'senha inválida',
|
||||||
'Last saved on:': 'Salvo em:',
|
'Invalid Query': 'Consulta inválida',
|
||||||
'License for': 'Licença para',
|
'invalid request': 'solicitação inválida',
|
||||||
'loading...': 'carregando...',
|
'invalid ticket': 'ticket inválido',
|
||||||
'locals': 'locals',
|
'Keyboard shortcuts': 'Atalhos de teclado',
|
||||||
'Login': 'Entrar',
|
'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado',
|
||||||
'login': 'inicio de sessão',
|
'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados',
|
||||||
'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
|
'languages': 'linguagens',
|
||||||
'Logout': 'finalizar sessão',
|
'Languages': 'Linguagens',
|
||||||
'Lost Password': 'Senha perdida',
|
'languages updated': 'linguagens atualizadas',
|
||||||
'Manage': 'Manage',
|
'Last name': 'Sobrenome',
|
||||||
'manage': 'gerenciar',
|
'Last saved on:': 'Salvo em:',
|
||||||
'merge': 'juntar',
|
'License for': 'Licença para',
|
||||||
'Models': 'Modelos',
|
'lists by ticket': 'lists by ticket',
|
||||||
'models': 'modelos',
|
'Loading...': 'Loading...',
|
||||||
'Modules': 'Módulos',
|
'loading...': 'carregando...',
|
||||||
'modules': 'módulos',
|
'Local Apps': 'Local Apps',
|
||||||
'Name': 'Nome',
|
'locals': 'locals',
|
||||||
'new application "%s" created': 'nova aplicação "%s" criada',
|
'Login': 'Entrar',
|
||||||
'New application wizard': 'Assistente para novas aplicações ',
|
'login': 'inicio de sessão',
|
||||||
'new plugin installed': 'novo plugin instalado',
|
'Login successful': 'Login successful',
|
||||||
'New Record': 'Novo registro',
|
'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
|
||||||
'new record inserted': 'novo registro inserido',
|
'Login/Register': 'Login/Register',
|
||||||
'New simple application': 'Nova aplicação básica',
|
'Logout': 'finalizar sessão',
|
||||||
'next 100 rows': 'próximos 100 registros',
|
'Lost Password': 'Senha perdida',
|
||||||
'NO': 'NÃO',
|
'manage': 'gerenciar',
|
||||||
'No databases in this application': 'Não existem bancos de dados nesta aplicação',
|
'Manage': 'Manage',
|
||||||
'no match': 'não encontrado',
|
'merge': 'juntar',
|
||||||
'no package selected': 'nenhum pacote selecionado',
|
'models': 'modelos',
|
||||||
'online designer': 'online designer',
|
'Models': 'Modelos',
|
||||||
'or alternatively': 'or alternatively',
|
'Modules': 'Módulos',
|
||||||
'Or Get from URL:': 'Ou Obtenha do URL:',
|
'modules': 'módulos',
|
||||||
'or import from csv file': 'ou importar de um arquivo CSV',
|
'Name': 'Nome',
|
||||||
'or provide app url:': 'ou forneça a url de uma aplicação:',
|
'new application "%s" created': 'nova aplicação "%s" criada',
|
||||||
'or provide application url:': 'ou forneça a url de uma aplicação:',
|
'New Application Wizard': 'New Application Wizard',
|
||||||
'Origin': 'Origem',
|
'New application wizard': 'Assistente para novas aplicações ',
|
||||||
'Original/Translation': 'Original/Tradução',
|
'new plugin installed': 'novo plugin instalado',
|
||||||
'Overwrite installed app': 'sobrescrever aplicação instalada',
|
'New Record': 'Novo registro',
|
||||||
'Pack all': 'criar pacote',
|
'new record inserted': 'novo registro inserido',
|
||||||
'Pack compiled': 'criar pacote compilado',
|
'New simple application': 'Nova aplicação básica',
|
||||||
'Pack custom': 'Pack custom',
|
'next 100 rows': 'próximos 100 registros',
|
||||||
'pack plugin': 'empacotar plugin',
|
'NO': 'NÃO',
|
||||||
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
|
'No databases in this application': 'Não existem bancos de dados nesta aplicação',
|
||||||
'Password': 'Senha',
|
'no match': 'não encontrado',
|
||||||
'password changed': 'senha alterada',
|
'no package selected': 'nenhum pacote selecionado',
|
||||||
'Peeking at file': 'Visualizando arquivo',
|
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
|
||||||
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" eliminado',
|
'online designer': 'online designer',
|
||||||
'Plugin "%s" in application': 'Plugin "%s" na aplicação',
|
'or alternatively': 'or alternatively',
|
||||||
'plugins': 'plugins',
|
'Or Get from URL:': 'Ou Obtenha do URL:',
|
||||||
'Plugins': 'Plugins',
|
'or import from csv file': 'ou importar de um arquivo CSV',
|
||||||
'Plural-Forms:': 'Plural-Forms:',
|
'or provide app url:': 'ou forneça a url de uma aplicação:',
|
||||||
'Powered by': 'Este site utiliza',
|
'or provide application url:': 'ou forneça a url de uma aplicação:',
|
||||||
'previous 100 rows': '100 registros anteriores',
|
'Origin': 'Origem',
|
||||||
'Private files': 'Private files',
|
'Original/Translation': 'Original/Tradução',
|
||||||
'private files': 'private files',
|
'Overwrite installed app': 'sobrescrever aplicação instalada',
|
||||||
'Query:': 'Consulta:',
|
'Pack all': 'criar pacote',
|
||||||
'Rapid Search': 'Rapid Search',
|
'Pack compiled': 'criar pacote compilado',
|
||||||
'record': 'registro',
|
'Pack custom': 'Pack custom',
|
||||||
'record does not exist': 'o registro não existe',
|
'pack plugin': 'empacotar plugin',
|
||||||
'record id': 'id do registro',
|
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
|
||||||
'Record ID': 'ID do Registro',
|
'Password': 'Senha',
|
||||||
'Register': 'Registrar-se',
|
'password changed': 'senha alterada',
|
||||||
'Registration key': 'Chave de registro',
|
'Peeking at file': 'Visualizando arquivo',
|
||||||
'Reload routes': 'Reload routes',
|
'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
|
||||||
'Remove compiled': 'eliminar compilados',
|
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" eliminado',
|
||||||
'Replace': 'Substituir',
|
'Plugin "%s" in application': 'Plugin "%s" na aplicação',
|
||||||
'Replace All': 'Substituir Tudo',
|
'plugins': 'plugins',
|
||||||
'request': 'request',
|
'Plugins': 'Plugins',
|
||||||
'Resolve Conflict file': 'Arquivo de resolução de conflito',
|
'Plural-Forms:': 'Plural-Forms:',
|
||||||
'response': 'response',
|
'Powered by': 'Este site utiliza',
|
||||||
'restore': 'restaurar',
|
'previous 100 rows': '100 registros anteriores',
|
||||||
'revert': 'reverter',
|
'Private files': 'Private files',
|
||||||
'Role': 'Papel',
|
'private files': 'private files',
|
||||||
'Rows in table': 'Registros na tabela',
|
'PythonAnywhere Apps': 'PythonAnywhere Apps',
|
||||||
'Rows selected': 'Registros selecionados',
|
'PythonAnywhere Password': 'PythonAnywhere Password',
|
||||||
'rules are not defined': 'rules are not defined',
|
'Query:': 'Consulta:',
|
||||||
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
|
'Rapid Search': 'Rapid Search',
|
||||||
'Running on %s': 'A correr em %s',
|
'Read': 'Read',
|
||||||
'Save': 'Save',
|
'record': 'registro',
|
||||||
'save': 'salvar',
|
'record does not exist': 'o registro não existe',
|
||||||
'Save file:': 'Gravar ficheiro:',
|
'record id': 'id do registro',
|
||||||
'Save file: %s': 'Gravar ficheiro: %s',
|
'Record ID': 'ID do Registro',
|
||||||
'Save via Ajax': 'Gravar via Ajax',
|
'Register': 'Registrar-se',
|
||||||
'Saved file hash:': 'Hash do arquivo salvo:',
|
'Registration key': 'Chave de registro',
|
||||||
'selected': 'selecionado(s)',
|
'Reload routes': 'Reload routes',
|
||||||
'session': 'session',
|
'Remove compiled': 'eliminar compilados',
|
||||||
'session expired': 'sessão expirada',
|
'Replace': 'Substituir',
|
||||||
'shell': 'Terminal',
|
'Replace All': 'Substituir Tudo',
|
||||||
'Site': 'site',
|
'request': 'request',
|
||||||
'some files could not be removed': 'alguns arquicos não puderam ser removidos',
|
'requires python-git, but not installed': 'requires python-git, but not installed',
|
||||||
'Start searching': 'Start searching',
|
'Resolve Conflict file': 'Arquivo de resolução de conflito',
|
||||||
'Start wizard': 'iniciar assistente',
|
'response': 'response',
|
||||||
'state': 'estado',
|
'restore': 'restaurar',
|
||||||
'Static': 'Static',
|
'revert': 'reverter',
|
||||||
'static': 'estáticos',
|
'Role': 'Papel',
|
||||||
'Static files': 'Arquivos estáticos',
|
'Rows in table': 'Registros na tabela',
|
||||||
'Submit': 'Submit',
|
'Rows selected': 'Registros selecionados',
|
||||||
'submit': 'enviar',
|
'rules are not defined': 'rules are not defined',
|
||||||
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
|
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
|
||||||
'table': 'tabela',
|
'Running on %s': 'A correr em %s',
|
||||||
'Table name': 'Nome da tabela',
|
'Save': 'Save',
|
||||||
'test': 'testar',
|
'save': 'salvar',
|
||||||
'Testing application': 'Testando a aplicação',
|
'Save file:': 'Gravar ficheiro:',
|
||||||
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.',
|
'Save file: %s': 'Gravar ficheiro: %s',
|
||||||
'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador',
|
'Save via Ajax': 'Gravar via Ajax',
|
||||||
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
|
'Saved file hash:': 'Hash do arquivo salvo:',
|
||||||
'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados',
|
'selected': 'selecionado(s)',
|
||||||
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
|
'session': 'session',
|
||||||
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
|
'session expired': 'sessão expirada',
|
||||||
'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates',
|
'shell': 'Terminal',
|
||||||
'There are no controllers': 'Não existem controllers',
|
'Site': 'site',
|
||||||
'There are no models': 'Não existem modelos',
|
'some files could not be removed': 'alguns arquicos não puderam ser removidos',
|
||||||
'There are no modules': 'Não existem módulos',
|
'Something went wrong please wait a few minutes before retrying': 'Something went wrong please wait a few minutes before retrying',
|
||||||
'There are no plugins': 'There are no plugins',
|
'source : filesystem': 'source : filesystem',
|
||||||
'There are no private files': '',
|
'Start a new app': 'Start a new app',
|
||||||
'There are no static files': 'Não existem arquicos estáticos',
|
'Start searching': 'Start searching',
|
||||||
'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada',
|
'Start wizard': 'iniciar assistente',
|
||||||
'There are no views': 'Não existem visões',
|
'state': 'estado',
|
||||||
'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',
|
'Static': 'Static',
|
||||||
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
|
'static': 'estáticos',
|
||||||
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
|
'Static files': 'Arquivos estáticos',
|
||||||
'This is the %(filename)s template': 'Este é o template %(filename)s',
|
'Submit': 'Submit',
|
||||||
'Ticket': 'Ticket',
|
'submit': 'enviar',
|
||||||
'Ticket ID': 'Ticket ID',
|
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
|
||||||
'Timestamp': 'Data Atual',
|
'switch to : db': 'switch to : db',
|
||||||
'TM': 'MR',
|
'table': 'tabela',
|
||||||
'to previous version.': 'para a versão anterior.',
|
'Table name': 'Nome da tabela',
|
||||||
'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]',
|
'test': 'testar',
|
||||||
'toggle breakpoint': 'toggle breakpoint',
|
'Testing application': 'Testando a aplicação',
|
||||||
'Toggle comment': 'Toggle comment',
|
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.',
|
||||||
'Toggle Fullscreen': 'Toggle Fullscreen',
|
'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador',
|
||||||
'Traceback': 'Traceback',
|
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
|
||||||
'translation strings for the application': 'textos traduzidos para a aplicação',
|
'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados',
|
||||||
'Translation strings for the application': 'Translation strings for the application',
|
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
|
||||||
'try': 'tente',
|
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
|
||||||
'try something like': 'tente algo como',
|
'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates',
|
||||||
'Try the mobile interface': 'Try the mobile interface',
|
'There are no controllers': 'Não existem controllers',
|
||||||
'Unable to check for upgrades': 'Não é possível checar as atualizações',
|
'There are no models': 'Não existem modelos',
|
||||||
'unable to create application "%s"': 'não é possível criar a aplicação "%s"',
|
'There are no modules': 'Não existem módulos',
|
||||||
'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"',
|
'There are no plugins': 'There are no plugins',
|
||||||
'unable to delete file plugin "%(plugin)s"': 'não é possível criar o plugin "%(plugin)s"',
|
'There are no private files': '',
|
||||||
'Unable to download': 'Não é possível efetuar o download',
|
'There are no static files': 'Não existem arquicos estáticos',
|
||||||
'Unable to download app': 'Não é possível baixar a aplicação',
|
'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada',
|
||||||
'Unable to download app because:': 'Não é possível baixar a aplicação porque:',
|
'There are no views': 'Não existem visões',
|
||||||
'Unable to download because': 'Não é possível baixar porque',
|
'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',
|
||||||
'unable to parse csv file': 'não é possível analisar o arquivo CSV',
|
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
|
||||||
'unable to uninstall "%s"': 'não é possível instalar "%s"',
|
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
|
||||||
'unable to upgrade because "%s"': 'não é possível atualizar porque "%s"',
|
'This is the %(filename)s template': 'Este é o template %(filename)s',
|
||||||
'uncheck all': 'desmarcar todos',
|
'Ticket': 'Ticket',
|
||||||
'Uninstall': 'desinstalar',
|
'Ticket ID': 'Ticket ID',
|
||||||
'update': 'atualizar',
|
'Timestamp': 'Data Atual',
|
||||||
'update all languages': 'atualizar todas as linguagens',
|
'TM': 'MR',
|
||||||
'Update:': 'Atualizar:',
|
'to previous version.': 'para a versão anterior.',
|
||||||
'upgrade web2py now': 'atualize o web2py agora',
|
'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]',
|
||||||
'upload': 'upload',
|
'toggle breakpoint': 'toggle breakpoint',
|
||||||
'Upload': 'Upload',
|
'Toggle comment': 'Toggle comment',
|
||||||
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
|
'Toggle Fullscreen': 'Toggle Fullscreen',
|
||||||
'Upload a package:': 'Faça upload de um pacote:',
|
'Traceback': 'Traceback',
|
||||||
'Upload and install packed application': 'Upload and install packed application',
|
'translation strings for the application': 'textos traduzidos para a aplicação',
|
||||||
'upload application:': 'Fazer upload de uma aplicação:',
|
'Translation strings for the application': 'Translation strings for the application',
|
||||||
'Upload existing application': 'Faça upload de uma aplicação existente',
|
'try': 'tente',
|
||||||
'upload file:': 'Enviar arquivo:',
|
'try something like': 'tente algo como',
|
||||||
'upload plugin file:': 'Enviar arquivo de plugin:',
|
'Try the mobile interface': 'Try the mobile interface',
|
||||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para criar consultas mais complexas.',
|
'Unable to check for upgrades': 'Não é possível checar as atualizações',
|
||||||
'Use an url:': 'Use uma url:',
|
'unable to create application "%s"': 'não é possível criar a aplicação "%s"',
|
||||||
'User ID': 'ID do Usuario',
|
'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"',
|
||||||
'variables': 'variáveis',
|
'unable to delete file plugin "%(plugin)s"': 'não é possível criar o plugin "%(plugin)s"',
|
||||||
'Version': 'Versão',
|
'Unable to download': 'Não é possível efetuar o download',
|
||||||
'versioning': 'versionamento',
|
'Unable to download app': 'Não é possível baixar a aplicação',
|
||||||
'Versioning': 'Versioning',
|
'Unable to download app because:': 'Não é possível baixar a aplicação porque:',
|
||||||
'view': 'visão',
|
'Unable to download because': 'Não é possível baixar porque',
|
||||||
'Views': 'Visões',
|
'unable to parse csv file': 'não é possível analisar o arquivo CSV',
|
||||||
'views': 'visões',
|
'unable to uninstall "%s"': 'não é possível instalar "%s"',
|
||||||
'Web Framework': 'Web Framework',
|
'unable to upgrade because "%s"': 'não é possível atualizar porque "%s"',
|
||||||
'web2py is up to date': 'web2py está atualizado',
|
'uncheck all': 'desmarcar todos',
|
||||||
'web2py Recent Tweets': 'Tweets Recentes de @web2py',
|
'Uninstall': 'desinstalar',
|
||||||
'web2py upgraded; please restart it': 'web2py atualizado; favor reiniciar',
|
'update': 'atualizar',
|
||||||
'Welcome to web2py': 'Bem-vindo ao web2py',
|
'update all languages': 'atualizar todas as linguagens',
|
||||||
'YES': 'SIM',
|
'Update:': 'Atualizar:',
|
||||||
}
|
'upgrade now to %s': 'upgrade now to %s',
|
||||||
|
'upgrade web2py now': 'atualize o web2py agora',
|
||||||
|
'upload': 'upload',
|
||||||
|
'Upload': 'Upload',
|
||||||
|
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
|
||||||
|
'Upload a package:': 'Faça upload de um pacote:',
|
||||||
|
'Upload and install packed application': 'Upload and install packed application',
|
||||||
|
'upload application:': 'Fazer upload de uma aplicação:',
|
||||||
|
'Upload existing application': 'Faça upload de uma aplicação existente',
|
||||||
|
'upload file:': 'Enviar arquivo:',
|
||||||
|
'upload plugin file:': 'Enviar arquivo de plugin:',
|
||||||
|
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para criar consultas mais complexas.',
|
||||||
|
'Use an url:': 'Use uma url:',
|
||||||
|
'User ID': 'ID do Usuario',
|
||||||
|
'Username': 'Username',
|
||||||
|
'variables': 'variáveis',
|
||||||
|
'Version': 'Versão',
|
||||||
|
'versioning': 'versionamento',
|
||||||
|
'Versioning': 'Versioning',
|
||||||
|
'view': 'visão',
|
||||||
|
'Views': 'Visões',
|
||||||
|
'views': 'visões',
|
||||||
|
'Warning!': 'Warning!',
|
||||||
|
'Web Framework': 'Web Framework',
|
||||||
|
'web2py Admin Password': 'web2py Admin Password',
|
||||||
|
'web2py is up to date': 'web2py está atualizado',
|
||||||
|
'web2py Recent Tweets': 'Tweets Recentes de @web2py',
|
||||||
|
'web2py upgraded; please restart it': 'web2py atualizado; favor reiniciar',
|
||||||
|
'Welcome to web2py': 'Bem-vindo ao web2py',
|
||||||
|
'YES': 'SIM',
|
||||||
|
'You only need these if you have already registered': 'You only need these if you have already registered',
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,8 +75,8 @@
|
|||||||
* this over and over... all will be bound to the document
|
* this over and over... all will be bound to the document
|
||||||
*/
|
*/
|
||||||
/*adds btn class to buttons*/
|
/*adds btn class to buttons*/
|
||||||
$('button', target).addClass('btn');
|
$('button:not([class^="btn"])', target).addClass('btn');
|
||||||
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
|
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn');
|
||||||
/* javascript for PasswordWidget*/
|
/* javascript for PasswordWidget*/
|
||||||
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
||||||
web2py.validate_entropy($(this));
|
web2py.validate_entropy($(this));
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
},
|
},
|
||||||
ajax_init: function(target) {
|
ajax_init: function(target) {
|
||||||
/*called whenever a fragment gets loaded */
|
/*called whenever a fragment gets loaded */
|
||||||
$('.hidden', target).hide();
|
$('.w2p_hidden', target).hide();
|
||||||
web2py.manage_errors(target);
|
web2py.manage_errors(target);
|
||||||
web2py.ajax_fields(target);
|
web2py.ajax_fields(target);
|
||||||
web2py.show_if_handler(target);
|
web2py.show_if_handler(target);
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
* and require no dom manipulations
|
* and require no dom manipulations
|
||||||
*/
|
*/
|
||||||
var doc = $(document);
|
var doc = $(document);
|
||||||
doc.on('click', '.flash', function(e) {
|
doc.on('click', '.w2p_flash', function(e) {
|
||||||
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();
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
/*personally I don't like it.
|
/*personally I don't like it.
|
||||||
*if there's an error it it flashed and can be removed
|
*if there's an error it it flashed and can be removed
|
||||||
*as any other message
|
*as any other message
|
||||||
*doc.off('click', '.flash')
|
*doc.off('click', '.w2p_flash')
|
||||||
*/
|
*/
|
||||||
switch(xhr.status) {
|
switch(xhr.status) {
|
||||||
case 500:
|
case 500:
|
||||||
@@ -257,12 +257,20 @@
|
|||||||
if(form.hasClass('no_trap')) {
|
if(form.hasClass('no_trap')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
form.attr('data-w2p_target', target);
|
|
||||||
|
var w2p_target = $(this).attr('data-w2p_target');
|
||||||
|
if (typeof w2p_target === typeof undefined || w2p_target === false) {
|
||||||
|
form.attr('data-w2p_target', target);
|
||||||
|
} else {
|
||||||
|
target = w2p_target;
|
||||||
|
}
|
||||||
|
|
||||||
var url = form.attr('action');
|
var url = form.attr('action');
|
||||||
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
|
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
|
||||||
/* form has no action. Use component url. */
|
/* form has no action. Use component url. */
|
||||||
url = action;
|
url = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -530,13 +538,13 @@
|
|||||||
},
|
},
|
||||||
/*helper for flash messages*/
|
/*helper for flash messages*/
|
||||||
flash: function(message, status) {
|
flash: function(message, status) {
|
||||||
var flash = $('.flash');
|
var flash = $('.w2p_flash');
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
flash.html(message).addClass(status);
|
flash.html(message).addClass(status);
|
||||||
if(flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
if(flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
||||||
},
|
},
|
||||||
hide_flash: function() {
|
hide_flash: function() {
|
||||||
$('.flash').fadeOut(0).html('');
|
$('.w2p_flash').fadeOut(0).html('');
|
||||||
},
|
},
|
||||||
show_if_handler: function(target) {
|
show_if_handler: function(target) {
|
||||||
var triggers = {};
|
var triggers = {};
|
||||||
@@ -692,7 +700,7 @@
|
|||||||
$.web2py.component_handler(target);
|
$.web2py.component_handler(target);
|
||||||
},
|
},
|
||||||
main_hook: function() {
|
main_hook: function() {
|
||||||
var flash = $('.flash');
|
var flash = $('.w2p_flash');
|
||||||
flash.hide();
|
flash.hide();
|
||||||
if(flash.html()) web2py.flash(flash.html());
|
if(flash.html()) web2py.flash(flash.html());
|
||||||
web2py.ajax_init(document);
|
web2py.ajax_init(document);
|
||||||
|
|||||||
@@ -137,9 +137,9 @@
|
|||||||
<h4>{{=T("Overview")}}</h4>
|
<h4>{{=T("Overview")}}</h4>
|
||||||
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
|
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
|
||||||
{{if total['entries'] > 0:}}
|
{{if total['entries'] > 0:}}
|
||||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses})",
|
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||||
dict(ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
|
dict( ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{=T("Size of cache:")}}
|
{{=T("Size of cache:")}}
|
||||||
{{if object_stats:}}
|
{{if object_stats:}}
|
||||||
@@ -155,8 +155,8 @@
|
|||||||
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
|
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle();')}}
|
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="all_keys">
|
<div class="w2p_hidden" id="all_keys">
|
||||||
{{=total['keys']}}
|
{{=total['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -183,8 +183,8 @@
|
|||||||
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
|
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle();')}}
|
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="ram_keys">
|
<div class="w2p_hidden" id="ram_keys">
|
||||||
{{=ram['keys']}}
|
{{=ram['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -212,8 +212,8 @@
|
|||||||
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
|
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle();')}}
|
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="disk_keys">
|
<div class="w2p_hidden" id="disk_keys">
|
||||||
{{=disk['keys']}}
|
{{=disk['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -249,8 +249,8 @@
|
|||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
|||||||
{{=peekfile('views',c, dict(id=id))}}
|
{{=peekfile('views',c, dict(id=id))}}
|
||||||
</span>
|
</span>
|
||||||
<span class="extras celled celled-one">
|
<span class="extras celled celled-one">
|
||||||
{{if extend.has_key(c):}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
|
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
|
||||||
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
|
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
|
|||||||
{{=peekfile('views',c)}}
|
{{=peekfile('views',c)}}
|
||||||
</span>
|
</span>
|
||||||
<span class="extras celled">
|
<span class="extras celled">
|
||||||
{{if extend.has_key(c):}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
|
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
|
||||||
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
|
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
{{buttons.append((URL('pack',args=a), T("Pack all")))}}
|
{{buttons.append((URL('pack',args=a), T("Pack all")))}}
|
||||||
{{buttons.append((URL('pack_custom',args=a), T("Pack custom")))}}
|
{{buttons.append((URL('pack_custom',args=a), T("Pack custom")))}}
|
||||||
{{if not os.path.exists('applications/%s/compiled' % a):}}
|
{{if not os.path.exists('applications/%s/compiled' % a):}}
|
||||||
{{buttons.append((URL('compile_app',args=a), T("Compile")))}}
|
{{buttons.append((URL('compile_app',args=[a, 'skip_failed_views']),
|
||||||
|
T("Compile (skip failed views)")))}}
|
||||||
|
{{buttons.append((URL('compile_app',args=a), T("Compile (all or nothing)")))}}
|
||||||
{{else:}}
|
{{else:}}
|
||||||
{{buttons.append((URL('pack',args=(a, 'compiled')), T("Pack compiled")))}}
|
{{buttons.append((URL('pack',args=(a, 'compiled')), T("Pack compiled")))}}
|
||||||
{{if glob.glob('applications/%s/controllers/*.py' % a):}}
|
{{if glob.glob('applications/%s/controllers/*.py' % a):}}
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
<p class="row-buttons">
|
<p class="row-buttons">
|
||||||
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
|
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
|
||||||
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
|
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
|
||||||
|
{{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
|
||||||
</p>
|
</p>
|
||||||
</div> <!-- /DEPLOY ON GAE -->
|
</div> <!-- /DEPLOY ON GAE -->
|
||||||
<!-- APP WIZARD -->
|
<!-- APP WIZARD -->
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
jQuery(document).ready(function(){
|
jQuery(document).ready(function(){
|
||||||
jQuery("[rel=tooltip]").tooltip();
|
jQuery("[rel=tooltip]").tooltip();
|
||||||
|
jQuery(":input").attr("autocomplete","off");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
{{extend 'layout.html'}}
|
||||||
|
<h2><span style="color:#139FD7">python</span>anywhere {{=T('Deployment Interface')}}</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="register_form">
|
||||||
|
<h3>{{=T('Login/Register')}}</h3>
|
||||||
|
<form class="form-horizontal" id="palogin">
|
||||||
|
|
||||||
|
<div class="control-group" id="username__row">
|
||||||
|
<label class="control-label" for="username">{{=T('Username')}}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" name="username" id="username"><span class="help-inline">*</span>
|
||||||
|
<span class="help-block"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group" id="email_address__row">
|
||||||
|
<label class="control-label" for="email_address">{{=T('Email Address')}}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" name="email_address" id="email_address">
|
||||||
|
<span class="help-block"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group" id="pythonanywhere_password__row">
|
||||||
|
<label class="control-label" for="pythonanywhere_password">{{=T('PythonAnywhere Password')}}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="password" name="pythonanywhere_password" id="pythonanywhere_password">
|
||||||
|
<span class="help-block"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group" id="web2py_admin_password__row">
|
||||||
|
<label class="control-label" for="web2py_admin_password">{{=T('web2py Admin Password')}}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="password" name="web2py_admin_password" id="web2py_admin_password"><span class="help-inline">*</span>
|
||||||
|
<span class="help-block"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group" id="accepts_terms__row">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" name="accepts_terms" id="accepts_terms"><a target="_blank" href="https://www.pythonanywhere.com/terms/">{{=T('Accept Terms')}}</a>
|
||||||
|
</label>
|
||||||
|
<span class="help-block"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<button type="submit" class="btn btn-primary" id="submit_palogin">{{=T('Submit')}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<p>* {{=T('You only need these if you have already registered')}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row-fluid" id="app_manager" style="display:none;">
|
||||||
|
<div class="span6">
|
||||||
|
<h3>{{=T('Local Apps')}}</h3>
|
||||||
|
<form id="apppicker">
|
||||||
|
<select name="apps" class="form-control" id="local" multiple>
|
||||||
|
<option>{{=T('Loading...')}}</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Deploy" id="deploy_button" class="btn btn-primary">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong>{{=T('Warning!')}}</strong> {{=T('if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="span6">
|
||||||
|
<h3>{{=T('PythonAnywhere Apps')}}</h3>
|
||||||
|
<ul id="pythonanywhere">
|
||||||
|
<li>{{=T('Loading...')}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
$('#palogin').off('submit');
|
||||||
|
$('#palogin').submit(function(event) {
|
||||||
|
var data = $('#palogin').serialize();
|
||||||
|
$.web2py.disableElement($('#submit_palogin'));
|
||||||
|
$.web2py.disableFormElements($('#palogin'));
|
||||||
|
$.ajax({
|
||||||
|
url: '{{=URL("pythonanywhere", "create_account")}}',
|
||||||
|
type: 'POST',
|
||||||
|
data: data,
|
||||||
|
dataType: 'json',
|
||||||
|
}).done(function(data, textStatus, jqXHR) {
|
||||||
|
$('#palogin .error').removeClass('error');
|
||||||
|
$('#palogin .help-block').text('');
|
||||||
|
if(data.status == 'error') {
|
||||||
|
for(var error in data.errors) {
|
||||||
|
$('#' + error + '__row').addClass('error');
|
||||||
|
$('#' + error + '__row .help-block').text(data.errors[error][0]);
|
||||||
|
}
|
||||||
|
$.web2py.enableElement($('#submit_palogin'));
|
||||||
|
$.web2py.enableFormElements($('#palogin'));
|
||||||
|
$.web2py.flash("{{=T('Form has errors')}}");
|
||||||
|
} else {
|
||||||
|
$.web2py.flash("{{=T('Login successful')}}");
|
||||||
|
$('#register_form').hide();
|
||||||
|
$('#app_manager').show();
|
||||||
|
refresh_apps();
|
||||||
|
}
|
||||||
|
}).fail(function(){
|
||||||
|
$.web2py.flash("{{=T('Something went wrong please wait a few minutes before retrying')}}");
|
||||||
|
$.web2py.enableElement($('#submit_palogin'));
|
||||||
|
$.web2py.enableFormElements($('#palogin'));
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#apppicker').off('submit');
|
||||||
|
$('#apppicker').submit(function(event) {
|
||||||
|
var data = $('#apppicker').serialize();
|
||||||
|
$.web2py.disableElement($('#deploy_button'));
|
||||||
|
$.ajax({
|
||||||
|
url: '{{=URL("pythonanywhere", "bulk_install")}}',
|
||||||
|
type: 'POST',
|
||||||
|
data: {username: $('#username').val(), password: $('#web2py_admin_password').val(), apps: $('#local').val()},
|
||||||
|
dataType: 'json',
|
||||||
|
}).done(function(data, textStatus, jqXHR) {
|
||||||
|
refresh_apps();
|
||||||
|
$.web2py.enableElement($('#deploy_button'));
|
||||||
|
}).fail(function(){
|
||||||
|
$.web2py.flash("{{=T('Something went wrong please wait a few minutes before retrying')}}");
|
||||||
|
$.web2py.enableElement($('#deploy_button'));
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function refresh_apps() {
|
||||||
|
// Refresh List of Apps
|
||||||
|
$('#deploy_button').prop('disabled', true);
|
||||||
|
$.ajax({
|
||||||
|
url: '{{=URL("pythonanywhere", "list_apps")}}',
|
||||||
|
type: 'GET',
|
||||||
|
data: {username: $('#username').val(), password: $('#web2py_admin_password').val()},
|
||||||
|
dataType: 'json',
|
||||||
|
}).done(function(data, textStatus, jqXHR) {
|
||||||
|
var i = 0;
|
||||||
|
$('#local').html('')
|
||||||
|
for(i = 0; i < data.local.length; i++) {
|
||||||
|
$('#local').append($('<option>', {
|
||||||
|
value: data.local[i],
|
||||||
|
text: data.local[i]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
$('#local').multiSelect('refresh');
|
||||||
|
$('#pythonanywhere').html('')
|
||||||
|
for(i = 0; i < data.pythonanywhere.length; i++) {
|
||||||
|
$('#pythonanywhere').append($('<li>', {
|
||||||
|
text: data.pythonanywhere[i]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
$('#deploy_button').prop('disabled', false);
|
||||||
|
$.web2py.hide_flash();
|
||||||
|
}).fail(function(){
|
||||||
|
// Mostly this happens if it's a new account, just waiting a bit should be enough.
|
||||||
|
$.get('http://' + $('#username').val() + '.pythonanywhere.com'); // Kickstart the instance
|
||||||
|
$.web2py.flash("{{=T('Please wait, giving pythonanywhere a moment...')}}");
|
||||||
|
setTimeout(refresh_apps, 30000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -445,30 +445,31 @@ def ccache():
|
|||||||
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
|
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
|
||||||
total.update(gae_stats)
|
total.update(gae_stats)
|
||||||
else:
|
else:
|
||||||
|
# get ram stats directly from the cache object
|
||||||
|
ram_stats = cache.ram.stats[request.application]
|
||||||
|
ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
|
||||||
|
ram['misses'] = ram_stats['misses']
|
||||||
|
try:
|
||||||
|
ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
|
||||||
|
except (KeyError, ZeroDivisionError):
|
||||||
|
ram['ratio'] = 0
|
||||||
|
|
||||||
for key, value in cache.ram.storage.iteritems():
|
for key, value in cache.ram.storage.iteritems():
|
||||||
if isinstance(value, dict):
|
if hp:
|
||||||
ram['hits'] = value['hit_total'] - value['misses']
|
ram['bytes'] += hp.iso(value[1]).size
|
||||||
ram['misses'] = value['misses']
|
ram['objects'] += hp.iso(value[1]).count
|
||||||
try:
|
ram['entries'] += 1
|
||||||
ram['ratio'] = ram['hits'] * 100 / value['hit_total']
|
if value[0] < ram['oldest']:
|
||||||
except (KeyError, ZeroDivisionError):
|
ram['oldest'] = value[0]
|
||||||
ram['ratio'] = 0
|
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
||||||
else:
|
|
||||||
if hp:
|
|
||||||
ram['bytes'] += hp.iso(value[1]).size
|
|
||||||
ram['objects'] += hp.iso(value[1]).count
|
|
||||||
ram['entries'] += 1
|
|
||||||
if value[0] < ram['oldest']:
|
|
||||||
ram['oldest'] = value[0]
|
|
||||||
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
|
||||||
|
|
||||||
for key in cache.disk.storage:
|
for key in cache.disk.storage:
|
||||||
value = cache.disk.storage[key]
|
value = cache.disk.storage[key]
|
||||||
if isinstance(value, dict):
|
if isinstance(value[1], dict):
|
||||||
disk['hits'] = value['hit_total'] - value['misses']
|
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
||||||
disk['misses'] = value['misses']
|
disk['misses'] = value[1]['misses']
|
||||||
try:
|
try:
|
||||||
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
|
disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
|
||||||
except (KeyError, ZeroDivisionError):
|
except (KeyError, ZeroDivisionError):
|
||||||
disk['ratio'] = 0
|
disk['ratio'] = 0
|
||||||
else:
|
else:
|
||||||
@@ -485,7 +486,7 @@ def ccache():
|
|||||||
ram_keys.remove('oldest')
|
ram_keys.remove('oldest')
|
||||||
for key in ram_keys:
|
for key in ram_keys:
|
||||||
total[key] = ram[key] + disk[key]
|
total[key] = ram[key] + disk[key]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
total['ratio'] = total['hits'] * 100 / (total['hits'] +
|
total['ratio'] = total['hits'] * 100 / (total['hits'] +
|
||||||
total['misses'])
|
total['misses'])
|
||||||
@@ -575,7 +576,7 @@ def bg_graph_model():
|
|||||||
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
||||||
|
|
||||||
group = meta_graphmodel['group'].replace(' ', '')
|
group = meta_graphmodel['group'].replace(' ', '')
|
||||||
if not subgraphs.has_key(group):
|
if group not in subgraphs:
|
||||||
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
||||||
subgraphs[group]['tables'].append(tablename)
|
subgraphs[group]['tables'].append(tablename)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#### Learning and Demos
|
#### Learning and Demos
|
||||||
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
|
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
|
||||||
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
|
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
|
||||||
|
- [[web2py Reference Project http://www.web2pyref.com/]]
|
||||||
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
|
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
|
||||||
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
|
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
|
||||||
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
|
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
|
||||||
|
|||||||
@@ -320,3 +320,10 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
||||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||||
|
.control-label.readonly{
|
||||||
|
padding-top:0px !important;
|
||||||
|
padding-right:0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,8 @@
|
|||||||
* this over and over... all will be bound to the document
|
* this over and over... all will be bound to the document
|
||||||
*/
|
*/
|
||||||
/*adds btn class to buttons*/
|
/*adds btn class to buttons*/
|
||||||
$('button', target).addClass('btn');
|
$('button:not([class^="btn"])', target).addClass('btn');
|
||||||
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
|
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn');
|
||||||
/* javascript for PasswordWidget*/
|
/* javascript for PasswordWidget*/
|
||||||
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
||||||
web2py.validate_entropy($(this));
|
web2py.validate_entropy($(this));
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
},
|
},
|
||||||
ajax_init: function(target) {
|
ajax_init: function(target) {
|
||||||
/*called whenever a fragment gets loaded */
|
/*called whenever a fragment gets loaded */
|
||||||
$('.hidden', target).hide();
|
$('.w2p_hidden', target).hide();
|
||||||
web2py.manage_errors(target);
|
web2py.manage_errors(target);
|
||||||
web2py.ajax_fields(target);
|
web2py.ajax_fields(target);
|
||||||
web2py.show_if_handler(target);
|
web2py.show_if_handler(target);
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
* and require no dom manipulations
|
* and require no dom manipulations
|
||||||
*/
|
*/
|
||||||
var doc = $(document);
|
var doc = $(document);
|
||||||
doc.on('click', '.flash', function(e) {
|
doc.on('click', '.w2p_flash', function(e) {
|
||||||
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();
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
/*personally I don't like it.
|
/*personally I don't like it.
|
||||||
*if there's an error it it flashed and can be removed
|
*if there's an error it it flashed and can be removed
|
||||||
*as any other message
|
*as any other message
|
||||||
*doc.off('click', '.flash')
|
*doc.off('click', '.w2p_flash')
|
||||||
*/
|
*/
|
||||||
switch(xhr.status) {
|
switch(xhr.status) {
|
||||||
case 500:
|
case 500:
|
||||||
@@ -257,12 +257,20 @@
|
|||||||
if(form.hasClass('no_trap')) {
|
if(form.hasClass('no_trap')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
form.attr('data-w2p_target', target);
|
|
||||||
|
var w2p_target = $(this).attr('data-w2p_target');
|
||||||
|
if (typeof w2p_target === typeof undefined || w2p_target === false) {
|
||||||
|
form.attr('data-w2p_target', target);
|
||||||
|
} else {
|
||||||
|
target = w2p_target;
|
||||||
|
}
|
||||||
|
|
||||||
var url = form.attr('action');
|
var url = form.attr('action');
|
||||||
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
|
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
|
||||||
/* form has no action. Use component url. */
|
/* form has no action. Use component url. */
|
||||||
url = action;
|
url = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -530,13 +538,13 @@
|
|||||||
},
|
},
|
||||||
/*helper for flash messages*/
|
/*helper for flash messages*/
|
||||||
flash: function(message, status) {
|
flash: function(message, status) {
|
||||||
var flash = $('.flash');
|
var flash = $('.w2p_flash');
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
flash.html(message).addClass(status);
|
flash.html(message).addClass(status);
|
||||||
if(flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
if(flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
||||||
},
|
},
|
||||||
hide_flash: function() {
|
hide_flash: function() {
|
||||||
$('.flash').fadeOut(0).html('');
|
$('.w2p_flash').fadeOut(0).html('');
|
||||||
},
|
},
|
||||||
show_if_handler: function(target) {
|
show_if_handler: function(target) {
|
||||||
var triggers = {};
|
var triggers = {};
|
||||||
@@ -692,7 +700,7 @@
|
|||||||
$.web2py.component_handler(target);
|
$.web2py.component_handler(target);
|
||||||
},
|
},
|
||||||
main_hook: function() {
|
main_hook: function() {
|
||||||
var flash = $('.flash');
|
var flash = $('.w2p_flash');
|
||||||
flash.hide();
|
flash.hide();
|
||||||
if(flash.html()) web2py.flash(flash.html());
|
if(flash.html()) web2py.flash(flash.html());
|
||||||
web2py.ajax_init(document);
|
web2py.ajax_init(document);
|
||||||
|
|||||||
@@ -137,9 +137,9 @@
|
|||||||
<h4>{{=T("Overview")}}</h4>
|
<h4>{{=T("Overview")}}</h4>
|
||||||
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
|
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
|
||||||
{{if total['entries'] > 0:}}
|
{{if total['entries'] > 0:}}
|
||||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses})",
|
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||||
dict(ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
|
dict( ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{=T("Size of cache:")}}
|
{{=T("Size of cache:")}}
|
||||||
{{if object_stats:}}
|
{{if object_stats:}}
|
||||||
@@ -155,8 +155,8 @@
|
|||||||
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
|
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle();')}}
|
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="all_keys">
|
<div class="w2p_hidden" id="all_keys">
|
||||||
{{=total['keys']}}
|
{{=total['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -183,8 +183,8 @@
|
|||||||
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
|
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle();')}}
|
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="ram_keys">
|
<div class="w2p_hidden" id="ram_keys">
|
||||||
{{=ram['keys']}}
|
{{=ram['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -212,8 +212,8 @@
|
|||||||
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
|
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle();')}}
|
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="disk_keys">
|
<div class="w2p_hidden" id="disk_keys">
|
||||||
{{=disk['keys']}}
|
{{=disk['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -249,8 +249,8 @@
|
|||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -445,30 +445,31 @@ def ccache():
|
|||||||
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
|
gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age'])
|
||||||
total.update(gae_stats)
|
total.update(gae_stats)
|
||||||
else:
|
else:
|
||||||
|
# get ram stats directly from the cache object
|
||||||
|
ram_stats = cache.ram.stats[request.application]
|
||||||
|
ram['hits'] = ram_stats['hit_total'] - ram_stats['misses']
|
||||||
|
ram['misses'] = ram_stats['misses']
|
||||||
|
try:
|
||||||
|
ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total']
|
||||||
|
except (KeyError, ZeroDivisionError):
|
||||||
|
ram['ratio'] = 0
|
||||||
|
|
||||||
for key, value in cache.ram.storage.iteritems():
|
for key, value in cache.ram.storage.iteritems():
|
||||||
if isinstance(value, dict):
|
if hp:
|
||||||
ram['hits'] = value['hit_total'] - value['misses']
|
ram['bytes'] += hp.iso(value[1]).size
|
||||||
ram['misses'] = value['misses']
|
ram['objects'] += hp.iso(value[1]).count
|
||||||
try:
|
ram['entries'] += 1
|
||||||
ram['ratio'] = ram['hits'] * 100 / value['hit_total']
|
if value[0] < ram['oldest']:
|
||||||
except (KeyError, ZeroDivisionError):
|
ram['oldest'] = value[0]
|
||||||
ram['ratio'] = 0
|
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
||||||
else:
|
|
||||||
if hp:
|
|
||||||
ram['bytes'] += hp.iso(value[1]).size
|
|
||||||
ram['objects'] += hp.iso(value[1]).count
|
|
||||||
ram['entries'] += 1
|
|
||||||
if value[0] < ram['oldest']:
|
|
||||||
ram['oldest'] = value[0]
|
|
||||||
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
|
|
||||||
|
|
||||||
for key in cache.disk.storage:
|
for key in cache.disk.storage:
|
||||||
value = cache.disk.storage[key]
|
value = cache.disk.storage[key]
|
||||||
if isinstance(value, dict):
|
if isinstance(value[1], dict):
|
||||||
disk['hits'] = value['hit_total'] - value['misses']
|
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
|
||||||
disk['misses'] = value['misses']
|
disk['misses'] = value[1]['misses']
|
||||||
try:
|
try:
|
||||||
disk['ratio'] = disk['hits'] * 100 / value['hit_total']
|
disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total']
|
||||||
except (KeyError, ZeroDivisionError):
|
except (KeyError, ZeroDivisionError):
|
||||||
disk['ratio'] = 0
|
disk['ratio'] = 0
|
||||||
else:
|
else:
|
||||||
@@ -485,7 +486,7 @@ def ccache():
|
|||||||
ram_keys.remove('oldest')
|
ram_keys.remove('oldest')
|
||||||
for key in ram_keys:
|
for key in ram_keys:
|
||||||
total[key] = ram[key] + disk[key]
|
total[key] = ram[key] + disk[key]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
total['ratio'] = total['hits'] * 100 / (total['hits'] +
|
total['ratio'] = total['hits'] * 100 / (total['hits'] +
|
||||||
total['misses'])
|
total['misses'])
|
||||||
@@ -575,7 +576,7 @@ def bg_graph_model():
|
|||||||
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
meta_graphmodel = dict(group=request.application, color='#ECECEC')
|
||||||
|
|
||||||
group = meta_graphmodel['group'].replace(' ', '')
|
group = meta_graphmodel['group'].replace(' ', '')
|
||||||
if not subgraphs.has_key(group):
|
if group not in subgraphs:
|
||||||
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
|
||||||
subgraphs[group]['tables'].append(tablename)
|
subgraphs[group]['tables'].append(tablename)
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ def user():
|
|||||||
http://..../[app]/default/user/profile
|
http://..../[app]/default/user/profile
|
||||||
http://..../[app]/default/user/retrieve_password
|
http://..../[app]/default/user/retrieve_password
|
||||||
http://..../[app]/default/user/change_password
|
http://..../[app]/default/user/change_password
|
||||||
http://..../[app]/default/user/manage_users (requires membership in
|
|
||||||
http://..../[app]/default/user/bulk_register
|
http://..../[app]/default/user/bulk_register
|
||||||
use @auth.requires_login()
|
use @auth.requires_login()
|
||||||
@auth.requires_membership('group name')
|
@auth.requires_membership('group name')
|
||||||
@auth.requires_permission('read','table name',record_id)
|
@auth.requires_permission('read','table name',record_id)
|
||||||
to decorate functions that need access control
|
to decorate functions that need access control
|
||||||
|
also notice there is http://..../[app]/appadmin/manage/auth to allow administrator to manage users
|
||||||
"""
|
"""
|
||||||
return dict(form=auth())
|
return dict(form=auth())
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+10
-2
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
div.flash {
|
div.w2p_flash {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
-o-border-radius: 4px;
|
-o-border-radius: 4px;
|
||||||
@@ -15,13 +15,13 @@ div.flash {
|
|||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
padding: 15px 35px 15px 15px;
|
padding: 15px 35px 15px 15px;
|
||||||
}
|
}
|
||||||
div.flash.alert:hover {
|
div.w2p_flash.alert:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.ie-lte8 div.flash {
|
.ie-lte8 div.w2p_flash {
|
||||||
filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0);
|
filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0);
|
||||||
}
|
}
|
||||||
.ie-lte8 div.flash:hover {
|
.ie-lte8 div.w2p_flash:hover {
|
||||||
filter: alpha(opacity=25);
|
filter: alpha(opacity=25);
|
||||||
}
|
}
|
||||||
.main-container {
|
.main-container {
|
||||||
@@ -37,7 +37,7 @@ div.error {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
div.flash.alert {
|
div.w2p_flash.alert {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 70px;
|
top: 70px;
|
||||||
@@ -136,7 +136,7 @@ header h1 {
|
|||||||
header .jumbotron {
|
header .jumbotron {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.flash {
|
.w2p_flash {
|
||||||
opacity: 0.9!important;
|
opacity: 0.9!important;
|
||||||
right: 100px;
|
right: 100px;
|
||||||
}
|
}
|
||||||
@@ -314,6 +314,3 @@ td.w2p_fc,
|
|||||||
input[type=checkbox], input[type=radio] {
|
input[type=checkbox], input[type=radio] {
|
||||||
margin: 4px 4px 0 0;
|
margin: 4px 4px 0 0;
|
||||||
}
|
}
|
||||||
.btn {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
@@ -33,7 +33,7 @@ audio {width:200px}
|
|||||||
[type="text"], [type="password"], select {
|
[type="text"], [type="password"], select {
|
||||||
margin-right: 5px; width: 300px;
|
margin-right: 5px; width: 300px;
|
||||||
}
|
}
|
||||||
.hidden {display:none;visibility:visible}
|
.w2p_hidden {display:none;visibility:visible}
|
||||||
.right {float:right; text-align:right}
|
.right {float:right; text-align:right}
|
||||||
.left {float:left; text-align:left}
|
.left {float:left; text-align:left}
|
||||||
.center {width:100%; text-align:center; vertical-align:middle}
|
.center {width:100%; text-align:center; vertical-align:middle}
|
||||||
@@ -88,7 +88,7 @@ div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; pa
|
|||||||
#web2py_user_form td {vertical-align:top}
|
#web2py_user_form td {vertical-align:top}
|
||||||
|
|
||||||
/*********** web2py specific ***********/
|
/*********** web2py specific ***********/
|
||||||
div.flash {
|
div.w2p_flash {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
display:none;
|
display:none;
|
||||||
position:fixed;
|
position:fixed;
|
||||||
@@ -117,11 +117,11 @@ div.flash {
|
|||||||
z-index:2000;
|
z-index:2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
|
div.w2p_flash #closeflash{color:inherit; float:right; margin-left:15px;}
|
||||||
.ie-lte7 div.flash #closeflash
|
.ie-lte7 div.flash #closeflash
|
||||||
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
|
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
|
||||||
|
|
||||||
div.flash:hover { opacity:0.25; }
|
div.w2p_flash:hover { opacity:0.25; }
|
||||||
|
|
||||||
div.error_wrapper {display:block}
|
div.error_wrapper {display:block}
|
||||||
div.error {
|
div.error {
|
||||||
@@ -304,8 +304,8 @@ li.w2p_grid_breadcrumb_elem {
|
|||||||
/* fix some IE problems */
|
/* fix some IE problems */
|
||||||
|
|
||||||
.ie-lte7 .topbar .container {z-index:2}
|
.ie-lte7 .topbar .container {z-index:2}
|
||||||
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
.ie-lte8 div.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
|
||||||
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
|
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);}
|
||||||
.ie9 #w2p_query_panel {padding-bottom:2px}
|
.ie9 #w2p_query_panel {padding-bottom:2px}
|
||||||
|
|
||||||
.web2py_console .form-control {width: 20%; display: inline;}
|
.web2py_console .form-control {width: 20%; display: inline;}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
* this over and over... all will be bound to the document
|
* this over and over... all will be bound to the document
|
||||||
*/
|
*/
|
||||||
/*adds btn class to buttons*/
|
/*adds btn class to buttons*/
|
||||||
$('button', target).addClass('btn btn-default');
|
$('button:not([class^="btn"])', target).addClass('btn btn-default');
|
||||||
$("p.w2p-autocomplete-widget input").addClass('form-control');
|
$("p.w2p-autocomplete-widget input").addClass('form-control');
|
||||||
$('form input[type="submit"], form input[type="button"]', target).addClass('btn btn-default');
|
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn btn-default');
|
||||||
/* javascript for PasswordWidget*/
|
/* javascript for PasswordWidget*/
|
||||||
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
||||||
web2py.validate_entropy($(this));
|
web2py.validate_entropy($(this));
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
function pe(ul, e) {
|
function pe(ul, e) {
|
||||||
var new_line = ml(ul);
|
var new_line = ml(ul);
|
||||||
rel(ul);
|
rel(ul);
|
||||||
if ($(e.target).parent().is(':visible')) {
|
if ($(e.target).closest('li').is(':visible')) {
|
||||||
/* make sure we didn't delete the element before we insert after */
|
/* make sure we didn't delete the element before we insert after */
|
||||||
new_line.insertAfter($(e.target).parent());
|
new_line.insertAfter($(e.target).closest('li'));
|
||||||
} else {
|
} else {
|
||||||
/* the line we clicked on was deleted, just add to end of list */
|
/* the line we clicked on was deleted, just add to end of list */
|
||||||
new_line.appendTo(ul);
|
new_line.appendTo(ul);
|
||||||
@@ -30,9 +30,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rl(ul, e) {
|
function rl(ul, e) {
|
||||||
if ($(ul).children().length > 1) {
|
if ($(ul).find('li').length > 1) {
|
||||||
/* only remove if we have more than 1 item so the list is never empty */
|
/* only remove if we have more than 1 item so the list is never empty */
|
||||||
$(e.target).parent().remove();
|
$(e.target).closest('li').remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +46,13 @@
|
|||||||
function rel(ul) {
|
function rel(ul) {
|
||||||
/* keep only as many as needed*/
|
/* keep only as many as needed*/
|
||||||
$(ul).find("li").each(function() {
|
$(ul).find("li").each(function() {
|
||||||
var trimmed = $.trim($(this.firstChild).val());
|
var trimmed = $.trim($(this).find(":text").val());
|
||||||
if (trimmed == '') $(this).remove();
|
if (trimmed == '') $(this).remove();
|
||||||
else $(this.firstChild).val(trimmed);
|
else $(this).find(":text").val(trimmed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var ul = this;
|
var ul = this;
|
||||||
$(ul).find(":text").after('<a class="btn btn-default" href="#">+</a> <a class="btn btn-default" href="#">-</a>').keypress(function(e) {
|
$(ul).find(":text").addClass('form-control').wrap("<div class='input-group'></div>").after('<div class="input-group-addon"><i class="glyphicon glyphicon-plus"></i></div><div class="input-group-addon"><i class="glyphicon glyphicon-minus"></i></div>').keypress(function(e) {
|
||||||
return (e.which == 13) ? pe(ul, e) : true;
|
return (e.which == 13) ? pe(ul, e) : true;
|
||||||
}).next().click(function(e) {
|
}).next().click(function(e) {
|
||||||
pe(ul, e);
|
pe(ul, e);
|
||||||
|
|||||||
@@ -75,8 +75,8 @@
|
|||||||
* this over and over... all will be bound to the document
|
* this over and over... all will be bound to the document
|
||||||
*/
|
*/
|
||||||
/*adds btn class to buttons*/
|
/*adds btn class to buttons*/
|
||||||
$('button', target).addClass('btn');
|
$('button:not([class^="btn"])', target).addClass('btn');
|
||||||
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
|
$('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn');
|
||||||
/* javascript for PasswordWidget*/
|
/* javascript for PasswordWidget*/
|
||||||
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
$('input[type=password][data-w2p_entropy]', target).each(function() {
|
||||||
web2py.validate_entropy($(this));
|
web2py.validate_entropy($(this));
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
},
|
},
|
||||||
ajax_init: function(target) {
|
ajax_init: function(target) {
|
||||||
/*called whenever a fragment gets loaded */
|
/*called whenever a fragment gets loaded */
|
||||||
$('.hidden', target).hide();
|
$('.w2p_hidden', target).hide();
|
||||||
web2py.manage_errors(target);
|
web2py.manage_errors(target);
|
||||||
web2py.ajax_fields(target);
|
web2py.ajax_fields(target);
|
||||||
web2py.show_if_handler(target);
|
web2py.show_if_handler(target);
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
* and require no dom manipulations
|
* and require no dom manipulations
|
||||||
*/
|
*/
|
||||||
var doc = $(document);
|
var doc = $(document);
|
||||||
doc.on('click', '.flash', function(e) {
|
doc.on('click', '.w2p_flash', function(e) {
|
||||||
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();
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
/*personally I don't like it.
|
/*personally I don't like it.
|
||||||
*if there's an error it it flashed and can be removed
|
*if there's an error it it flashed and can be removed
|
||||||
*as any other message
|
*as any other message
|
||||||
*doc.off('click', '.flash')
|
*doc.off('click', '.w2p_flash')
|
||||||
*/
|
*/
|
||||||
switch(xhr.status) {
|
switch(xhr.status) {
|
||||||
case 500:
|
case 500:
|
||||||
@@ -257,12 +257,20 @@
|
|||||||
if(form.hasClass('no_trap')) {
|
if(form.hasClass('no_trap')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
form.attr('data-w2p_target', target);
|
|
||||||
|
var w2p_target = $(this).attr('data-w2p_target');
|
||||||
|
if (typeof w2p_target === typeof undefined || w2p_target === false) {
|
||||||
|
form.attr('data-w2p_target', target);
|
||||||
|
} else {
|
||||||
|
target = w2p_target;
|
||||||
|
}
|
||||||
|
|
||||||
var url = form.attr('action');
|
var url = form.attr('action');
|
||||||
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
|
if((url === "") || (url === "#") || (typeof url === 'undefined')) {
|
||||||
/* form has no action. Use component url. */
|
/* form has no action. Use component url. */
|
||||||
url = action;
|
url = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -530,13 +538,13 @@
|
|||||||
},
|
},
|
||||||
/*helper for flash messages*/
|
/*helper for flash messages*/
|
||||||
flash: function(message, status) {
|
flash: function(message, status) {
|
||||||
var flash = $('.flash');
|
var flash = $('.w2p_flash');
|
||||||
web2py.hide_flash();
|
web2py.hide_flash();
|
||||||
flash.html(message).addClass(status);
|
flash.html(message).addClass(status);
|
||||||
if(flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
if(flash.html()) flash.append('<span id="closeflash"> × </span>').slideDown();
|
||||||
},
|
},
|
||||||
hide_flash: function() {
|
hide_flash: function() {
|
||||||
$('.flash').fadeOut(0).html('');
|
$('.w2p_flash').fadeOut(0).html('');
|
||||||
},
|
},
|
||||||
show_if_handler: function(target) {
|
show_if_handler: function(target) {
|
||||||
var triggers = {};
|
var triggers = {};
|
||||||
@@ -692,7 +700,7 @@
|
|||||||
$.web2py.component_handler(target);
|
$.web2py.component_handler(target);
|
||||||
},
|
},
|
||||||
main_hook: function() {
|
main_hook: function() {
|
||||||
var flash = $('.flash');
|
var flash = $('.w2p_flash');
|
||||||
flash.hide();
|
flash.hide();
|
||||||
if(flash.html()) web2py.flash(flash.html());
|
if(flash.html()) web2py.flash(flash.html());
|
||||||
web2py.ajax_init(document);
|
web2py.ajax_init(document);
|
||||||
|
|||||||
@@ -137,9 +137,9 @@
|
|||||||
<h4>{{=T("Overview")}}</h4>
|
<h4>{{=T("Overview")}}</h4>
|
||||||
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
|
<p>{{=T.M("Number of entries: **%s**", total['entries'])}}</p>
|
||||||
{{if total['entries'] > 0:}}
|
{{if total['entries'] > 0:}}
|
||||||
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses})",
|
<p>{{=T.M("Hit Ratio: **%(ratio)s%%** (**%(hits)s** %%{hit(hits)} and **%(misses)s** %%{miss(misses)})",
|
||||||
dict(ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
|
dict( ratio=total['ratio'], hits=total['hits'], misses=total['misses']))}}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{=T("Size of cache:")}}
|
{{=T("Size of cache:")}}
|
||||||
{{if object_stats:}}
|
{{if object_stats:}}
|
||||||
@@ -155,8 +155,8 @@
|
|||||||
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
|
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle();')}}
|
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="all_keys">
|
<div class="w2p_hidden" id="all_keys">
|
||||||
{{=total['keys']}}
|
{{=total['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -183,8 +183,8 @@
|
|||||||
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
|
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle();')}}
|
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="ram_keys">
|
<div class="w2p_hidden" id="ram_keys">
|
||||||
{{=ram['keys']}}
|
{{=ram['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -212,8 +212,8 @@
|
|||||||
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
|
||||||
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
|
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
|
||||||
</p>
|
</p>
|
||||||
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle();')}}
|
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
|
||||||
<div class="hidden" id="disk_keys">
|
<div class="w2p_hidden" id="disk_keys">
|
||||||
{{=disk['keys']}}
|
{{=disk['keys']}}
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
@@ -249,8 +249,8 @@
|
|||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
|
||||||
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
<meta name="google-site-verification" content="">
|
<meta name="google-site-verification" content="">
|
||||||
<!-- include stylesheets -->
|
<!-- include stylesheets -->
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/bootstrap-theme.min.css')}}"/>
|
|
||||||
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/>
|
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/>
|
||||||
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
|
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
|
||||||
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
|
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
|
||||||
@@ -47,9 +46,9 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
|
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
|
||||||
<div class="flash alert alert-dismissable">{{=response.flash or ''}}</div>
|
<div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
|
||||||
<!-- Navbar ======================================= -->
|
<!-- Navbar ======================================= -->
|
||||||
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||||
|
|||||||
Vendored
+150
@@ -0,0 +1,150 @@
|
|||||||
|
from fabric.api import *
|
||||||
|
from fabric.operations import put, get
|
||||||
|
from fabric.contrib.files import exists
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
env.hosts = env.hosts or raw_input('hostname (example.com):').split(',')
|
||||||
|
env.user = env.user or raw_input('username :')
|
||||||
|
|
||||||
|
INSTALL_SCRIPT = "setup-web2py-nginx-uwsgi-ubuntu.sh"
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
applications = '/home/www-data/web2py/applications'
|
||||||
|
|
||||||
|
def create_user(username):
|
||||||
|
"""fab -H root@host create_user:username"""
|
||||||
|
password = getpass.getpass(name+' password for %s> ' % username)
|
||||||
|
run('useradd -m %s' % username)
|
||||||
|
run('usermod --password %s %s' % (crypt.crypt(password, 'salt'), username))
|
||||||
|
run('mkdir -p ~%s/.ssh' % username)
|
||||||
|
run('cp /etc/sudoers /tmp/sudoers.new')
|
||||||
|
append('/tmp/sudoers.new', '%s ALL=NOPASSWD: ALL' % username, use_sudo=True)
|
||||||
|
run('visudo -c -f /tmp/sudoers.new')
|
||||||
|
run('EDITOR="cp /tmp/sudoers.new" visudo')
|
||||||
|
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
|
||||||
|
local('ssh-copy-id %s' % env.hosts[0])
|
||||||
|
|
||||||
|
def install_web2py():
|
||||||
|
"""fab -H username@host install_web2py"""
|
||||||
|
sudo('wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/%s' % INSTALL_SCRIPT)
|
||||||
|
sudo('chmod +x %s' % INSTALL_SCRIPT)
|
||||||
|
sudo('./'+INSTALL_SCRIPT)
|
||||||
|
|
||||||
|
def start_webserver():
|
||||||
|
sudo('service nginx start')
|
||||||
|
sudo('start uwsgi-emperor')
|
||||||
|
sudo('start web2py-scheduler')
|
||||||
|
|
||||||
|
def stop_webserver():
|
||||||
|
sudo('stop uwsgi-emperor')
|
||||||
|
sudo('service nginx stop')
|
||||||
|
sudo('stop web2py-scheduler')
|
||||||
|
|
||||||
|
def restart_webserver():
|
||||||
|
stop_webserver()
|
||||||
|
start_webserver()
|
||||||
|
|
||||||
|
def notify(appname=None):
|
||||||
|
"""fab -H username@host notify:appname"""
|
||||||
|
appname = appname or os.path.split(os.getcwd())[-1]
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('echo "response.flash = \'System Going Down For Maintenance\'" > models/flash_goingdown.py')
|
||||||
|
|
||||||
|
def down(appname=None):
|
||||||
|
"""fab -H username@host down:appname"""
|
||||||
|
appname = appname or os.path.split(os.getcwd())[-1]
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('echo `date` > DISABLED')
|
||||||
|
sudo('rm -rf sessions/* || true')
|
||||||
|
|
||||||
|
def up(appname=None):
|
||||||
|
"""fab -H username@host up:appname"""
|
||||||
|
appname = appname or os.path.split(os.getcwd())[-1]
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
with cd(appfolder):
|
||||||
|
if exists('modules/flash_goingdown.py'):
|
||||||
|
sudo('rm modules/flash_goingdown.py')
|
||||||
|
sudo('rm DISABLED')
|
||||||
|
|
||||||
|
def mkdir_or_backup(appname):
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
if not exists(appfolder):
|
||||||
|
sudo('mkdir %s' % appfolder)
|
||||||
|
sudo('chown -R www-data:www-data %s' % appfolder)
|
||||||
|
backup = None
|
||||||
|
else:
|
||||||
|
dt = now.strftime('%y-%m-%d-%h-%m')
|
||||||
|
backup = '%s.%s.zip' % (appname, dt)
|
||||||
|
with cd(applications):
|
||||||
|
sudo('zip -r %s %s' % (backup, appname))
|
||||||
|
return backup
|
||||||
|
|
||||||
|
def git_deploy(appname, repo):
|
||||||
|
"""fab -H username@host git_deploy:appname,username/remoname"""
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
backup = mkdir_or_backup(appfolder)
|
||||||
|
|
||||||
|
if exists(appfolder):
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('git pull origin master')
|
||||||
|
sudo('chown -R www-data:www-data *')
|
||||||
|
else:
|
||||||
|
with cd(applications):
|
||||||
|
sudo('git clone git@github.com/%s %s' % (repo, name))
|
||||||
|
sudo('chown -R www-data:www-data %s' % name)
|
||||||
|
|
||||||
|
def retrieve(appname=None):
|
||||||
|
"""fab -H username@host retrieve:appname"""
|
||||||
|
appname = appname or os.path.split(os.getcwd())[-1]
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
filename = '%s.zip' % appname
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('zip -r /tmp/%s *' % filename)
|
||||||
|
get('/tmp/%s' % filename, filename)
|
||||||
|
sudo('rm /tmp/%s' % filename)
|
||||||
|
local('unzip %s' % filename)
|
||||||
|
local('rm %s' % filename)
|
||||||
|
|
||||||
|
def deploy(appname=None, all=False):
|
||||||
|
"""fab -H username@host deploy:appname,all"""
|
||||||
|
appname = appname or os.path.split(os.getcwd())[-1]
|
||||||
|
appfolder = applications+'/'+appname
|
||||||
|
if os.path.exists('_update.zip'):
|
||||||
|
os.unlink('_update.zip')
|
||||||
|
|
||||||
|
backup = mkdir_or_backup(appfolder)
|
||||||
|
|
||||||
|
if all=='all' or not backup:
|
||||||
|
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
|
||||||
|
else:
|
||||||
|
local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*')
|
||||||
|
put('_update.zip','/tmp/_update.zip')
|
||||||
|
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('unzip -o /tmp/_update.zip')
|
||||||
|
sudo('chown -R www-data:www-data *')
|
||||||
|
sudo('echo "%s" > DATE_DEPLOYMENT' % now)
|
||||||
|
|
||||||
|
if backup:
|
||||||
|
print 'TO RESTORE: fab restore:%s' % backup
|
||||||
|
|
||||||
|
def restore(backup):
|
||||||
|
"""fab -H username@host restore:backupfilename"""
|
||||||
|
appname = backup.split('/')[-1].split('.')[0]
|
||||||
|
appfolder = applications + '/' + appname
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('rm -r *')
|
||||||
|
with cd(applications):
|
||||||
|
sudo('unzip %s' % backup)
|
||||||
|
sudo('chown -R www-data:www-data %s' % appname)
|
||||||
|
|
||||||
|
def cleanup(appname):
|
||||||
|
appname = appname or os.path.split(os.getcwd())[-1]
|
||||||
|
appfolder = applications + '/' + appname
|
||||||
|
with cd(appfolder):
|
||||||
|
sudo('rm -rf sessions/* || true')
|
||||||
|
sudo('rm -rf errors/* || true')
|
||||||
|
sudo('rm -rf cache/* || true')
|
||||||
+4
-4
@@ -120,7 +120,7 @@ def app_cleanup(app, request):
|
|||||||
r = False
|
r = False
|
||||||
|
|
||||||
# Remove cache files
|
# Remove cache files
|
||||||
path = apath('%s/cache/' % app, request)
|
path = apath('%s/cache/' % app, request)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
CacheOnDisk(folder=path).clear()
|
CacheOnDisk(folder=path).clear()
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
@@ -131,7 +131,7 @@ def app_cleanup(app, request):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def app_compile(app, request):
|
def app_compile(app, request, skip_failed_views=False):
|
||||||
"""Compiles the application
|
"""Compiles the application
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -145,8 +145,8 @@ def app_compile(app, request):
|
|||||||
from compileapp import compile_application, remove_compiled_application
|
from compileapp import compile_application, remove_compiled_application
|
||||||
folder = apath(app, request)
|
folder = apath(app, request)
|
||||||
try:
|
try:
|
||||||
compile_application(folder)
|
failed_views = compile_application(folder, skip_failed_views)
|
||||||
return None
|
return failed_views
|
||||||
except (Exception, RestrictedError):
|
except (Exception, RestrictedError):
|
||||||
tb = traceback.format_exc(sys.exc_info)
|
tb = traceback.format_exc(sys.exc_info)
|
||||||
remove_compiled_application(folder)
|
remove_compiled_application(folder)
|
||||||
|
|||||||
+14
-10
@@ -58,7 +58,7 @@ def remove_oldest_entries(storage, percentage=90):
|
|||||||
# compute current memory usage (%)
|
# compute current memory usage (%)
|
||||||
old_mem = psutil.virtual_memory().percent
|
old_mem = psutil.virtual_memory().percent
|
||||||
# if we have data in storage and utilization exceeds 90%
|
# if we have data in storage and utilization exceeds 90%
|
||||||
while storage and old_mem > percentage:
|
while storage and old_mem > percentage:
|
||||||
# removed oldest entry
|
# removed oldest entry
|
||||||
storage.popitem(last=False)
|
storage.popitem(last=False)
|
||||||
# garbage collect
|
# garbage collect
|
||||||
@@ -378,7 +378,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
|
|
||||||
|
|
||||||
def safe_apply(self, key, function, default_value=None):
|
def safe_apply(self, key, function, default_value=None):
|
||||||
"""
|
"""
|
||||||
Safely apply a function to the value of a key in storage and set
|
Safely apply a function to the value of a key in storage and set
|
||||||
the return value of the function to it.
|
the return value of the function to it.
|
||||||
|
|
||||||
@@ -606,22 +606,26 @@ class Cache(object):
|
|||||||
def wrapped_f():
|
def wrapped_f():
|
||||||
if current.request.env.request_method != 'GET':
|
if current.request.env.request_method != 'GET':
|
||||||
return func()
|
return func()
|
||||||
|
|
||||||
|
if quick:
|
||||||
|
session_ = True if 'S' in quick else False
|
||||||
|
vars_ = True if 'V' in quick else False
|
||||||
|
lang_ = True if 'L' in quick else False
|
||||||
|
user_agent_ = True if 'U' in quick else False
|
||||||
|
public_ = True if 'P' in quick else False
|
||||||
|
else:
|
||||||
|
(session_, vars_, lang_, user_agent_, public_) = \
|
||||||
|
(session, vars, lang, user_agent, public)
|
||||||
|
|
||||||
if time_expire:
|
if time_expire:
|
||||||
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
|
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
|
||||||
if quick:
|
|
||||||
session_ = True if 'S' in quick else False
|
|
||||||
vars_ = True if 'V' in quick else False
|
|
||||||
lang_ = True if 'L' in quick else False
|
|
||||||
user_agent_ = True if 'U' in quick else False
|
|
||||||
public_ = True if 'P' in quick else False
|
|
||||||
else:
|
|
||||||
session_, vars_, lang_, user_agent_, public_ = session, vars, lang, user_agent, public
|
|
||||||
if not session_ and public_:
|
if not session_ and public_:
|
||||||
cache_control += ', public'
|
cache_control += ', public'
|
||||||
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||||
else:
|
else:
|
||||||
cache_control += ', private'
|
cache_control += ', private'
|
||||||
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
|
||||||
|
|
||||||
if cache_model:
|
if cache_model:
|
||||||
#figure out the correct cache key
|
#figure out the correct cache key
|
||||||
cache_key = [current.request.env.path_info, current.response.view]
|
cache_key = [current.request.env.path_info, current.response.view]
|
||||||
|
|||||||
+37
-34
@@ -464,22 +464,28 @@ def read_pyc(filename):
|
|||||||
return marshal.loads(data[8:])
|
return marshal.loads(data[8:])
|
||||||
|
|
||||||
|
|
||||||
def compile_views(folder):
|
def compile_views(folder, skip_failed_views=False):
|
||||||
"""
|
"""
|
||||||
Compiles all the views in the application specified by `folder`
|
Compiles all the views in the application specified by `folder`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = pjoin(folder, 'views')
|
path = pjoin(folder, 'views')
|
||||||
|
failed_views = []
|
||||||
for fname in listdir(path, '^[\w/\-]+(\.\w+)*$'):
|
for fname in listdir(path, '^[\w/\-]+(\.\w+)*$'):
|
||||||
try:
|
try:
|
||||||
data = parse_template(fname, path)
|
data = parse_template(fname, path)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
raise Exception("%s in %s" % (e, fname))
|
if skip_failed_views:
|
||||||
filename = 'views.%s.py' % fname.replace(os.path.sep, '.')
|
failed_views.append(fname)
|
||||||
filename = pjoin(folder, 'compiled', filename)
|
else:
|
||||||
write_file(filename, data)
|
raise Exception("%s in %s" % (e, fname))
|
||||||
save_pyc(filename)
|
else:
|
||||||
os.unlink(filename)
|
filename = ('views/%s.py' % fname).replace('/', '_').replace('\\', '_')
|
||||||
|
filename = pjoin(folder, 'compiled', filename)
|
||||||
|
write_file(filename, data)
|
||||||
|
save_pyc(filename)
|
||||||
|
os.unlink(filename)
|
||||||
|
return failed_views if failed_views else None
|
||||||
|
|
||||||
|
|
||||||
def compile_models(folder):
|
def compile_models(folder):
|
||||||
@@ -652,7 +658,7 @@ def run_view_in(environment):
|
|||||||
"""
|
"""
|
||||||
request = current.request
|
request = current.request
|
||||||
response = current.response
|
response = current.response
|
||||||
view = response.view
|
view = environment['response'].view
|
||||||
folder = request.folder
|
folder = request.folder
|
||||||
path = pjoin(folder, 'compiled')
|
path = pjoin(folder, 'compiled')
|
||||||
badv = 'invalid view (%s)' % view
|
badv = 'invalid view (%s)' % view
|
||||||
@@ -667,32 +673,28 @@ def run_view_in(environment):
|
|||||||
ccode = parse_template(view, pjoin(folder, 'views'),
|
ccode = parse_template(view, pjoin(folder, 'views'),
|
||||||
context=environment)
|
context=environment)
|
||||||
restricted(ccode, environment, 'file stream')
|
restricted(ccode, environment, 'file stream')
|
||||||
elif os.path.exists(path):
|
|
||||||
x = view.replace('/', '.')
|
|
||||||
files = ['views.%s.pyc' % x]
|
|
||||||
if allow_generic:
|
|
||||||
files.append('views.generic.%s.pyc' % request.extension)
|
|
||||||
# for backward compatibility
|
|
||||||
x = view.replace('/', '_')
|
|
||||||
files.append('views_%s.pyc' % x)
|
|
||||||
if allow_generic:
|
|
||||||
files.append('views_generic.%s.pyc' % request.extension)
|
|
||||||
if request.extension == 'html':
|
|
||||||
files.append('views_%s.pyc' % x[:-5])
|
|
||||||
if allow_generic:
|
|
||||||
files.append('views_generic.pyc')
|
|
||||||
# end backward compatibility code
|
|
||||||
for f in files:
|
|
||||||
filename = pjoin(path, f)
|
|
||||||
if os.path.exists(filename):
|
|
||||||
code = read_pyc(filename)
|
|
||||||
restricted(code, environment, layer=filename)
|
|
||||||
return
|
|
||||||
raise HTTP(404,
|
|
||||||
rewrite.THREAD_LOCAL.routes.error_message % badv,
|
|
||||||
web2py_error=badv)
|
|
||||||
else:
|
else:
|
||||||
filename = pjoin(folder, 'views', view)
|
filename = pjoin(folder, 'views', view)
|
||||||
|
if os.path.exists(path): # compiled views
|
||||||
|
x = view.replace('/', '_')
|
||||||
|
files = ['views_%s.pyc' % x]
|
||||||
|
is_compiled = os.path.exists(pjoin(path, files[0]))
|
||||||
|
# Don't use a generic view if the non-compiled view exists.
|
||||||
|
if is_compiled or (not is_compiled and not os.path.exists(filename)):
|
||||||
|
if allow_generic:
|
||||||
|
files.append('views_generic.%s.pyc' % request.extension)
|
||||||
|
# for backward compatibility
|
||||||
|
if request.extension == 'html':
|
||||||
|
files.append('views_%s.pyc' % x[:-5])
|
||||||
|
if allow_generic:
|
||||||
|
files.append('views_generic.pyc')
|
||||||
|
# end backward compatibility code
|
||||||
|
for f in files:
|
||||||
|
compiled = pjoin(path, f)
|
||||||
|
if os.path.exists(compiled):
|
||||||
|
code = read_pyc(compiled)
|
||||||
|
restricted(code, environment, layer=compiled)
|
||||||
|
return
|
||||||
if not os.path.exists(filename) and allow_generic:
|
if not os.path.exists(filename) and allow_generic:
|
||||||
view = 'generic.' + request.extension
|
view = 'generic.' + request.extension
|
||||||
filename = pjoin(folder, 'views', view)
|
filename = pjoin(folder, 'views', view)
|
||||||
@@ -726,7 +728,7 @@ def remove_compiled_application(folder):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def compile_application(folder):
|
def compile_application(folder, skip_failed_views=False):
|
||||||
"""
|
"""
|
||||||
Compiles all models, views, controller for the application in `folder`.
|
Compiles all models, views, controller for the application in `folder`.
|
||||||
"""
|
"""
|
||||||
@@ -734,7 +736,8 @@ def compile_application(folder):
|
|||||||
os.mkdir(pjoin(folder, 'compiled'))
|
os.mkdir(pjoin(folder, 'compiled'))
|
||||||
compile_models(folder)
|
compile_models(folder)
|
||||||
compile_controllers(folder)
|
compile_controllers(folder)
|
||||||
compile_views(folder)
|
failed_views = compile_views(folder, skip_failed_views)
|
||||||
|
return failed_views
|
||||||
|
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def video(url):
|
|||||||
|
|
||||||
|
|
||||||
def googledoc_viewer(url):
|
def googledoc_viewer(url):
|
||||||
return '<iframe src="http://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url)
|
return '<iframe src="https://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url)
|
||||||
|
|
||||||
|
|
||||||
def web2py_component(url):
|
def web2py_component(url):
|
||||||
|
|||||||
@@ -6,15 +6,17 @@
|
|||||||
#
|
#
|
||||||
# Given the model
|
# Given the model
|
||||||
#
|
#
|
||||||
# db.define_table("table_name", Field("picture", "upload"), Field("thumbnail", "upload"))
|
# db.define_table("table_name", Field("picture", "upload"),
|
||||||
|
# Field("thumbnail", "upload"))
|
||||||
#
|
#
|
||||||
# # to resize the picture on upload
|
# to resize the picture on upload
|
||||||
#
|
#
|
||||||
# from images import RESIZE
|
# from images import RESIZE
|
||||||
#
|
#
|
||||||
# db.table_name.picture.requires = RESIZE(200, 200)
|
# db.table_name.picture.requires = RESIZE(200, 200)
|
||||||
#
|
#
|
||||||
# # to store original image in picture and create a thumbnail in 'thumbnail' field
|
# to store original image in picture and create a thumbnail
|
||||||
|
# in 'thumbnail' field
|
||||||
#
|
#
|
||||||
# from images import THUMB
|
# from images import THUMB
|
||||||
# db.table_name.thumbnail.compute = lambda row: THUMB(row.picture, 200, 200)
|
# db.table_name.thumbnail.compute = lambda row: THUMB(row.picture, 200, 200)
|
||||||
@@ -24,8 +26,11 @@ from gluon import current
|
|||||||
|
|
||||||
|
|
||||||
class RESIZE(object):
|
class RESIZE(object):
|
||||||
def __init__(self, nx=160, ny=80, error_message=' image resize'):
|
|
||||||
(self.nx, self.ny, self.error_message) = (nx, ny, error_message)
|
def __init__(self, nx=160, ny=80, quality=100,
|
||||||
|
error_message=' image resize'):
|
||||||
|
(self.nx, self.ny, self.quality, self.error_message) = (
|
||||||
|
nx, ny, quality, error_message)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
if isinstance(value, str) and len(value) == 0:
|
if isinstance(value, str) and len(value) == 0:
|
||||||
@@ -36,7 +41,7 @@ class RESIZE(object):
|
|||||||
img = Image.open(value.file)
|
img = Image.open(value.file)
|
||||||
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
|
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
|
||||||
s = cStringIO.StringIO()
|
s = cStringIO.StringIO()
|
||||||
img.save(s, 'JPEG', quality=100)
|
img.save(s, 'JPEG', quality=self.quality)
|
||||||
s.seek(0)
|
s.seek(0)
|
||||||
value.file = s
|
value.file = s
|
||||||
except:
|
except:
|
||||||
@@ -51,7 +56,7 @@ def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
|
|||||||
request = current.request
|
request = current.request
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import os
|
import os
|
||||||
img = Image.open(os.path.join(request.folder,'uploads',image))
|
img = Image.open(os.path.join(request.folder, 'uploads', image))
|
||||||
img.thumbnail((nx, ny), Image.ANTIALIAS)
|
img.thumbnail((nx, ny), Image.ANTIALIAS)
|
||||||
root, ext = os.path.splitext(image)
|
root, ext = os.path.splitext(image)
|
||||||
thumb = '%s_%s%s' % (root, name, ext)
|
thumb = '%s_%s%s' % (root, name, ext)
|
||||||
|
|||||||
@@ -14,12 +14,20 @@ except Exception, e:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def ldap_auth(server='ldap', port=None,
|
def ldap_auth(server='ldap',
|
||||||
|
port=None,
|
||||||
base_dn='ou=users,dc=domain,dc=com',
|
base_dn='ou=users,dc=domain,dc=com',
|
||||||
mode='uid', secure=False,
|
mode='uid',
|
||||||
cert_path=None, cert_file=None,
|
secure=False,
|
||||||
cacert_path=None, cacert_file=None, key_file=None,
|
self_signed_certificate=None, # See NOTE below
|
||||||
bind_dn=None, bind_pw=None, filterstr='objectClass=*',
|
cert_path=None,
|
||||||
|
cert_file=None,
|
||||||
|
cacert_path=None,
|
||||||
|
cacert_file=None,
|
||||||
|
key_file=None,
|
||||||
|
bind_dn=None,
|
||||||
|
bind_pw=None,
|
||||||
|
filterstr='objectClass=*',
|
||||||
username_attrib='uid',
|
username_attrib='uid',
|
||||||
custom_scope='subtree',
|
custom_scope='subtree',
|
||||||
allowed_groups=None,
|
allowed_groups=None,
|
||||||
@@ -159,6 +167,14 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
You can set the logging level with the "logging_level" parameter, default
|
You can set the logging level with the "logging_level" parameter, default
|
||||||
is "error" and can be set to error, warning, info, debug.
|
is "error" and can be set to error, warning, info, debug.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self_signed_certificate:
|
||||||
|
# NOTE : If you have a self-signed SSL Certificate pointing over "port=686" and "secure=True" alone
|
||||||
|
# will not work, you need also to set "self_signed_certificate=True".
|
||||||
|
# Ref1: https://onemoretech.wordpress.com/2015/06/25/connecting-to-ldap-over-self-signed-tls-with-python/
|
||||||
|
# Ref2: http://bneijt.nl/blog/post/connecting-to-ldaps-with-self-signed-cert-using-python/
|
||||||
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||||
|
|
||||||
logger = logging.getLogger('web2py.auth.ldap_auth')
|
logger = logging.getLogger('web2py.auth.ldap_auth')
|
||||||
if logging_level == 'error':
|
if logging_level == 'error':
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.ERROR)
|
||||||
@@ -196,8 +212,7 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
logger.warning('blank password not allowed')
|
logger.warning('blank password not allowed')
|
||||||
return False
|
return False
|
||||||
logger.debug('mode: [%s] manage_user: [%s] custom_scope: [%s]'
|
logger.debug('mode: [%s] manage_user: [%s] custom_scope: [%s]'
|
||||||
' manage_groups: [%s]' % (str(mode), str(manage_user),
|
' manage_groups: [%s]' % (str(mode), str(manage_user), str(custom_scope), str(manage_groups)))
|
||||||
str(custom_scope), str(manage_groups)))
|
|
||||||
if manage_user:
|
if manage_user:
|
||||||
if user_firstname_attrib.count(':') > 0:
|
if user_firstname_attrib.count(':') > 0:
|
||||||
(user_firstname_attrib,
|
(user_firstname_attrib,
|
||||||
@@ -246,14 +261,10 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
# in the ldap_basedn
|
# in the ldap_basedn
|
||||||
requested_attrs = ['sAMAccountName']
|
requested_attrs = ['sAMAccountName']
|
||||||
if manage_user:
|
if manage_user:
|
||||||
requested_attrs.extend([user_firstname_attrib,
|
requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
|
||||||
user_lastname_attrib,
|
|
||||||
user_mail_attrib])
|
|
||||||
result = con.search_ext_s(
|
result = con.search_ext_s(
|
||||||
ldap_basedn, ldap.SCOPE_SUBTREE,
|
ldap_basedn, ldap.SCOPE_SUBTREE,
|
||||||
"(&(sAMAccountName=%s)(%s))" % (
|
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr),
|
||||||
ldap.filter.escape_filter_chars(username_bare),
|
|
||||||
filterstr),
|
|
||||||
requested_attrs)[0][1]
|
requested_attrs)[0][1]
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
# result should be a dict in the form
|
# result should be a dict in the form
|
||||||
@@ -286,25 +297,21 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
if manage_user:
|
if manage_user:
|
||||||
result = con.search_s(dn, ldap.SCOPE_BASE,
|
result = con.search_s(dn, ldap.SCOPE_BASE,
|
||||||
"(objectClass=*)",
|
"(objectClass=*)",
|
||||||
[user_firstname_attrib,
|
[user_firstname_attrib, user_lastname_attrib, user_mail_attrib])[0][1]
|
||||||
user_lastname_attrib,
|
|
||||||
user_mail_attrib])[0][1]
|
|
||||||
|
|
||||||
if ldap_mode == 'uid':
|
if ldap_mode == 'uid':
|
||||||
# OpenLDAP (UID)
|
# OpenLDAP (UID)
|
||||||
if ldap_binddn and ldap_bindpw:
|
if ldap_binddn and ldap_bindpw:
|
||||||
con.simple_bind_s(ldap_binddn, ldap_bindpw)
|
con.simple_bind_s(ldap_binddn, ldap_bindpw)
|
||||||
dn = "uid=" + username + "," + ldap_basedn
|
dn = "uid=" + username + "," + ldap_basedn
|
||||||
dn = con.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, "(uid=%s)"%username, [''])[0][0]
|
dn = con.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, "(uid=%s)" % username, [''])[0][0]
|
||||||
else:
|
else:
|
||||||
dn = "uid=" + username + "," + ldap_basedn
|
dn = "uid=" + username + "," + ldap_basedn
|
||||||
con.simple_bind_s(dn, password)
|
con.simple_bind_s(dn, password)
|
||||||
if manage_user:
|
if manage_user:
|
||||||
result = con.search_s(dn, ldap.SCOPE_BASE,
|
result = con.search_s(dn, ldap.SCOPE_BASE,
|
||||||
"(objectClass=*)",
|
"(objectClass=*)",
|
||||||
[user_firstname_attrib,
|
[user_firstname_attrib, user_lastname_attrib, user_mail_attrib])[0][1]
|
||||||
user_lastname_attrib,
|
|
||||||
user_mail_attrib])[0][1]
|
|
||||||
|
|
||||||
if ldap_mode == 'company':
|
if ldap_mode == 'company':
|
||||||
# no DNs or password needed to search directory
|
# no DNs or password needed to search directory
|
||||||
@@ -319,9 +326,7 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
# find the uid
|
# find the uid
|
||||||
attrs = ['uid']
|
attrs = ['uid']
|
||||||
if manage_user:
|
if manage_user:
|
||||||
attrs.extend([user_firstname_attrib,
|
attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
|
||||||
user_lastname_attrib,
|
|
||||||
user_mail_attrib])
|
|
||||||
# perform the actual search
|
# perform the actual search
|
||||||
company_search_result = con.search_s(ldap_basedn,
|
company_search_result = con.search_s(ldap_basedn,
|
||||||
ldap.SCOPE_SUBTREE,
|
ldap.SCOPE_SUBTREE,
|
||||||
@@ -337,13 +342,11 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
basedns = ldap_basedn
|
basedns = ldap_basedn
|
||||||
else:
|
else:
|
||||||
basedns = [ldap_basedn]
|
basedns = [ldap_basedn]
|
||||||
filter = '(&(uid=%s)(%s))' % (
|
filter = '(&(uid=%s)(%s))' % (ldap.filter.escape_filter_chars(username), filterstr)
|
||||||
ldap.filter.escape_filter_chars(username), filterstr)
|
|
||||||
found = False
|
found = False
|
||||||
for basedn in basedns:
|
for basedn in basedns:
|
||||||
try:
|
try:
|
||||||
result = con.search_s(basedn, ldap.SCOPE_SUBTREE,
|
result = con.search_s(basedn, ldap.SCOPE_SUBTREE, filter)
|
||||||
filter)
|
|
||||||
if result:
|
if result:
|
||||||
user_dn = result[0][0]
|
user_dn = result[0][0]
|
||||||
# Check the password
|
# Check the password
|
||||||
@@ -352,9 +355,10 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
break
|
break
|
||||||
except ldap.LDAPError, detail:
|
except ldap.LDAPError, detail:
|
||||||
(exc_type, exc_value) = sys.exc_info()[:2]
|
(exc_type, exc_value) = sys.exc_info()[:2]
|
||||||
logger.warning(
|
logger.warning("ldap_auth: searching %s for %s resulted in %s: %s\n" % (basedn,
|
||||||
"ldap_auth: searching %s for %s resulted in %s: %s\n" %
|
filter,
|
||||||
(basedn, filter, exc_type, exc_value)
|
exc_type,
|
||||||
|
exc_value)
|
||||||
)
|
)
|
||||||
if not found:
|
if not found:
|
||||||
logger.warning('User [%s] not found!' % username)
|
logger.warning('User [%s] not found!' % username)
|
||||||
@@ -367,10 +371,7 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
basedns = ldap_basedn
|
basedns = ldap_basedn
|
||||||
else:
|
else:
|
||||||
basedns = [ldap_basedn]
|
basedns = [ldap_basedn]
|
||||||
filter = '(&(%s=%s)(%s))' % (username_attrib,
|
filter = '(&(%s=%s)(%s))' % (username_attrib, ldap.filter.escape_filter_chars(username), filterstr)
|
||||||
ldap.filter.escape_filter_chars(
|
|
||||||
username),
|
|
||||||
filterstr)
|
|
||||||
if custom_scope == 'subtree':
|
if custom_scope == 'subtree':
|
||||||
ldap_scope = ldap.SCOPE_SUBTREE
|
ldap_scope = ldap.SCOPE_SUBTREE
|
||||||
elif custom_scope == 'base':
|
elif custom_scope == 'base':
|
||||||
@@ -389,9 +390,10 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
break
|
break
|
||||||
except ldap.LDAPError, detail:
|
except ldap.LDAPError, detail:
|
||||||
(exc_type, exc_value) = sys.exc_info()[:2]
|
(exc_type, exc_value) = sys.exc_info()[:2]
|
||||||
logger.warning(
|
logger.warning("ldap_auth: searching %s for %s resulted in %s: %s\n" % (basedn,
|
||||||
"ldap_auth: searching %s for %s resulted in %s: %s\n" %
|
filter,
|
||||||
(basedn, filter, exc_type, exc_value)
|
exc_type,
|
||||||
|
exc_value)
|
||||||
)
|
)
|
||||||
if not found:
|
if not found:
|
||||||
logger.warning('User [%s] not found!' % username)
|
logger.warning('User [%s] not found!' % username)
|
||||||
@@ -401,16 +403,14 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
logger.info('[%s] Manage user data' % str(username))
|
logger.info('[%s] Manage user data' % str(username))
|
||||||
try:
|
try:
|
||||||
if user_firstname_part is not None:
|
if user_firstname_part is not None:
|
||||||
store_user_firstname = result[user_firstname_attrib][
|
store_user_firstname = result[user_firstname_attrib][0].split(' ', 1)[user_firstname_part]
|
||||||
0].split(' ', 1)[user_firstname_part]
|
|
||||||
else:
|
else:
|
||||||
store_user_firstname = result[user_firstname_attrib][0]
|
store_user_firstname = result[user_firstname_attrib][0]
|
||||||
except KeyError, e:
|
except KeyError, e:
|
||||||
store_user_firstname = None
|
store_user_firstname = None
|
||||||
try:
|
try:
|
||||||
if user_lastname_part is not None:
|
if user_lastname_part is not None:
|
||||||
store_user_lastname = result[user_lastname_attrib][
|
store_user_lastname = result[user_lastname_attrib][0].split(' ', 1)[user_lastname_part]
|
||||||
0].split(' ', 1)[user_lastname_part]
|
|
||||||
else:
|
else:
|
||||||
store_user_lastname = result[user_lastname_attrib][0]
|
store_user_lastname = result[user_lastname_attrib][0]
|
||||||
except KeyError, e:
|
except KeyError, e:
|
||||||
@@ -419,32 +419,27 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
store_user_mail = result[user_mail_attrib][0]
|
store_user_mail = result[user_mail_attrib][0]
|
||||||
except KeyError, e:
|
except KeyError, e:
|
||||||
store_user_mail = None
|
store_user_mail = None
|
||||||
try:
|
update_or_insert_values = {'first_name': store_user_firstname,
|
||||||
#
|
'last_name': store_user_lastname,
|
||||||
|
'email': store_user_mail}
|
||||||
|
if '@' not in username:
|
||||||
# user as username
|
# user as username
|
||||||
# #################
|
# ################
|
||||||
|
fields = ['first_name', 'last_name', 'email']
|
||||||
user_in_db = db(db.auth_user.username == username)
|
user_in_db = db(db.auth_user.username == username)
|
||||||
if user_in_db.count() > 0:
|
elif '@' in username:
|
||||||
user_in_db.update(first_name=store_user_firstname,
|
|
||||||
last_name=store_user_lastname,
|
|
||||||
email=store_user_mail)
|
|
||||||
else:
|
|
||||||
db.auth_user.insert(first_name=store_user_firstname,
|
|
||||||
last_name=store_user_lastname,
|
|
||||||
email=store_user_mail,
|
|
||||||
username=username)
|
|
||||||
except:
|
|
||||||
#
|
|
||||||
# user as email
|
# user as email
|
||||||
# ##############
|
# #############
|
||||||
|
fields = ['first_name', 'last_name']
|
||||||
user_in_db = db(db.auth_user.email == username)
|
user_in_db = db(db.auth_user.email == username)
|
||||||
if user_in_db.count() > 0:
|
update_or_insert_values = dict(((f, update_or_insert_values[f]) for f in fields))
|
||||||
user_in_db.update(first_name=store_user_firstname,
|
|
||||||
last_name=store_user_lastname)
|
if user_in_db.count() > 0:
|
||||||
else:
|
actual_values = user_in_db.select(*[db.auth_user[f] for f in fields]).first().as_dict()
|
||||||
db.auth_user.insert(first_name=store_user_firstname,
|
if update_or_insert_values != actual_values: # We don't update record if values are the same
|
||||||
last_name=store_user_lastname,
|
user_in_db.update(**update_or_insert_values)
|
||||||
email=username)
|
else:
|
||||||
|
db.auth_user.insert(**update_or_insert_values)
|
||||||
con.unbind()
|
con.unbind()
|
||||||
|
|
||||||
if manage_groups:
|
if manage_groups:
|
||||||
@@ -486,9 +481,7 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
# No match
|
# No match
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def do_manage_groups(username,
|
def do_manage_groups(username, password=None, db=db):
|
||||||
password=None,
|
|
||||||
db=db):
|
|
||||||
"""
|
"""
|
||||||
Manage user groups
|
Manage user groups
|
||||||
|
|
||||||
@@ -508,23 +501,19 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
# 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
|
||||||
# #############################################################
|
# #############################################################
|
||||||
try:
|
try:
|
||||||
db_user_id = db(db.auth_user.username == username).select(
|
db_user_id = db(db.auth_user.username == username).select(db.auth_user.id).first().id
|
||||||
db.auth_user.id).first().id
|
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
db_user_id = db(db.auth_user.email == username).select(
|
db_user_id = db(db.auth_user.email == username).select(db.auth_user.id).first().id
|
||||||
db.auth_user.id).first().id
|
|
||||||
except AttributeError, e:
|
except AttributeError, e:
|
||||||
#
|
#
|
||||||
# There is no user in local db
|
# There is no user in local db
|
||||||
# We create one
|
# We create one
|
||||||
# ##############################
|
# ##############################
|
||||||
try:
|
try:
|
||||||
db_user_id = db.auth_user.insert(username=username,
|
db_user_id = db.auth_user.insert(username=username, first_name=username)
|
||||||
first_name=username)
|
|
||||||
except AttributeError, e:
|
except AttributeError, e:
|
||||||
db_user_id = db.auth_user.insert(email=username,
|
db_user_id = db.auth_user.insert(email=username, first_name=username)
|
||||||
first_name=username)
|
|
||||||
if not db_user_id:
|
if not db_user_id:
|
||||||
logging.error(
|
logging.error(
|
||||||
'There is no username or email for %s!' % username)
|
'There is no username or email for %s!' % username)
|
||||||
@@ -532,27 +521,23 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
# 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
|
||||||
db_can_join = db.can_join() if hasattr(db, 'can_join') else True
|
db_can_join = db.can_join() if hasattr(db, 'can_join') else True
|
||||||
if db_can_join:
|
if db_can_join:
|
||||||
db_group_search = db(
|
db_group_search = \
|
||||||
(db.auth_membership.user_id == db_user_id) &
|
db((db.auth_membership.user_id == db_user_id) &
|
||||||
(db.auth_user.id == db.auth_membership.user_id) &
|
(db.auth_user.id == db.auth_membership.user_id) &
|
||||||
(db.auth_group.id == db.auth_membership.group_id))
|
(db.auth_group.id == db.auth_membership.group_id))
|
||||||
else:
|
else:
|
||||||
# no joins on NoSQL databases, perform two queries
|
# no joins on NoSQL databases, perform two queries
|
||||||
db_group_search = db(db.auth_membership.user_id == db_user_id)
|
db_group_search = db(db.auth_membership.user_id == db_user_id)
|
||||||
group_ids = [x.group_id for x in db_group_search.select(
|
group_ids = [x.group_id for x in db_group_search.select(db.auth_membership.group_id, distinct=True)]
|
||||||
db.auth_membership.group_id, distinct=True)]
|
|
||||||
db_group_search = db(db.auth_group.id.belongs(group_ids))
|
db_group_search = db(db.auth_group.id.belongs(group_ids))
|
||||||
db_groups_of_the_user = list()
|
db_groups_of_the_user = list()
|
||||||
db_group_id = dict()
|
db_group_id = dict()
|
||||||
|
|
||||||
if db_group_search.count() > 0:
|
if db_group_search.count() > 0:
|
||||||
for group in db_group_search.select(db.auth_group.id,
|
for group in db_group_search.select(db.auth_group.id, db.auth_group.role, distinct=True):
|
||||||
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' %
|
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
|
||||||
(username, str(db_groups_of_the_user)))
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Delete user membership from groups where user is not anymore
|
# Delete user membership from groups where user is not anymore
|
||||||
@@ -560,8 +545,7 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
for group_to_del in db_groups_of_the_user:
|
for group_to_del in db_groups_of_the_user:
|
||||||
if ldap_groups_of_the_user.count(group_to_del) == 0:
|
if ldap_groups_of_the_user.count(group_to_del) == 0:
|
||||||
db((db.auth_membership.user_id == db_user_id) &
|
db((db.auth_membership.user_id == db_user_id) &
|
||||||
(db.auth_membership.group_id == \
|
(db.auth_membership.group_id == db_group_id[group_to_del])).delete()
|
||||||
db_group_id[group_to_del])).delete()
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Create user membership in groups where user is not in already
|
# Create user membership in groups where user is not in already
|
||||||
@@ -569,16 +553,12 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
for group_to_add in ldap_groups_of_the_user:
|
for group_to_add in ldap_groups_of_the_user:
|
||||||
if db_groups_of_the_user.count(group_to_add) == 0:
|
if db_groups_of_the_user.count(group_to_add) == 0:
|
||||||
if db(db.auth_group.role == group_to_add).count() == 0:
|
if db(db.auth_group.role == group_to_add).count() == 0:
|
||||||
gid = db.auth_group.insert(role=group_to_add,
|
gid = db.auth_group.insert(role=group_to_add, description='Generated from LDAP')
|
||||||
description='Generated from LDAP')
|
|
||||||
else:
|
else:
|
||||||
gid = db(db.auth_group.role == group_to_add).select(
|
gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id
|
||||||
db.auth_group.id).first().id
|
db.auth_membership.insert(user_id=db_user_id, group_id=gid)
|
||||||
db.auth_membership.insert(user_id=db_user_id,
|
|
||||||
group_id=gid)
|
|
||||||
except:
|
except:
|
||||||
logger.warning("[%s] Groups are not managed successfully!" %
|
logger.warning("[%s] Groups are not managed successfully!" % str(username))
|
||||||
str(username))
|
|
||||||
import traceback
|
import traceback
|
||||||
logger.debug(traceback.format_exc())
|
logger.debug(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
@@ -669,10 +649,12 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
con.simple_bind_s(username, password)
|
con.simple_bind_s(username, password)
|
||||||
logger.debug('Ldap username connect...')
|
logger.debug('Ldap username connect...')
|
||||||
# We have to use the full string
|
# We have to use the full string
|
||||||
username = con.search_ext_s(base_dn, ldap.SCOPE_SUBTREE,
|
username = \
|
||||||
"(&(sAMAccountName=%s)(%s))" %
|
con.search_ext_s(base_dn,
|
||||||
(ldap.filter.escape_filter_chars(username_bare),
|
ldap.SCOPE_SUBTREE,
|
||||||
filterstr), ["cn"])[0][0]
|
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare),
|
||||||
|
filterstr),
|
||||||
|
["cn"])[0][0]
|
||||||
else:
|
else:
|
||||||
if ldap_binddn:
|
if ldap_binddn:
|
||||||
# need to search directory with an bind_dn account 1st
|
# need to search directory with an bind_dn account 1st
|
||||||
@@ -685,18 +667,14 @@ def ldap_auth(server='ldap', port=None,
|
|||||||
if username is None:
|
if username is None:
|
||||||
return list()
|
return list()
|
||||||
# search for groups where user is in
|
# search for groups where user is in
|
||||||
filter = '(&(%s=%s)(%s))' % (ldap.filter.escape_filter_chars(
|
filter = '(&(%s=%s)(%s))' % (ldap.filter.escape_filter_chars(group_member_attrib),
|
||||||
group_member_attrib
|
|
||||||
),
|
|
||||||
ldap.filter.escape_filter_chars(username),
|
ldap.filter.escape_filter_chars(username),
|
||||||
group_filterstr)
|
group_filterstr)
|
||||||
group_search_result = con.search_s(group_dn,
|
group_search_result = con.search_s(group_dn, ldap.SCOPE_SUBTREE, filter, [group_name_attrib])
|
||||||
ldap.SCOPE_SUBTREE,
|
|
||||||
filter, [group_name_attrib])
|
|
||||||
ldap_groups_of_the_user = list()
|
ldap_groups_of_the_user = list()
|
||||||
for group_row in group_search_result:
|
for group_row in group_search_result:
|
||||||
group = group_row[1]
|
group = group_row[1]
|
||||||
if type(group) == dict and group.has_key(group_name_attrib):
|
if type(group) == dict and group_name_attrib in group:
|
||||||
ldap_groups_of_the_user.extend(group[group_name_attrib])
|
ldap_groups_of_the_user.extend(group[group_name_attrib])
|
||||||
|
|
||||||
con.unbind()
|
con.unbind()
|
||||||
|
|||||||
@@ -139,24 +139,36 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
|||||||
Return the access token generated by the authenticating server.
|
Return the access token generated by the authenticating server.
|
||||||
|
|
||||||
If token is already in the session that one will be used.
|
If token is already in the session that one will be used.
|
||||||
|
If token has expired refresh_token is used to get another token.
|
||||||
Otherwise the token is fetched from the auth server.
|
Otherwise the token is fetched from the auth server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
refresh_token = None
|
||||||
if current.session.token and 'expires' in current.session.token:
|
if current.session.token and 'expires' in current.session.token:
|
||||||
expires = current.session.token['expires']
|
expires = current.session.token['expires']
|
||||||
# reuse token until expiration
|
# reuse token until expiration
|
||||||
if expires == 0 or expires > time.time():
|
if expires == 0 or expires > time.time():
|
||||||
return current.session.token['access_token']
|
return current.session.token['access_token']
|
||||||
|
if 'refresh_token' in current.session.token:
|
||||||
|
refresh_token = current.session.token['refresh_token']
|
||||||
|
|
||||||
code = current.request.vars.code
|
code = current.request.vars.code
|
||||||
|
|
||||||
if code:
|
if code or refresh_token:
|
||||||
data = dict(client_id=self.client_id,
|
data = dict(
|
||||||
client_secret=self.client_secret,
|
client_id=self.client_id,
|
||||||
redirect_uri=current.session.redirect_uri,
|
client_secret=self.client_secret,
|
||||||
code=code,
|
)
|
||||||
grant_type='authorization_code'
|
if code:
|
||||||
)
|
data.update(
|
||||||
|
redirect_uri=current.session.redirect_uri,
|
||||||
|
code=code,
|
||||||
|
grant_type='authorization_code'
|
||||||
|
)
|
||||||
|
elif refresh_token:
|
||||||
|
data.update(
|
||||||
|
refresh_token=refresh_token,
|
||||||
|
grant_type='refresh_token'
|
||||||
|
)
|
||||||
|
|
||||||
open_url = None
|
open_url = None
|
||||||
opener = self.__build_url_opener(self.token_url)
|
opener = self.__build_url_opener(self.token_url)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Include in your model (eg db.py)::
|
|||||||
|
|
||||||
auth.define_tables(username=True)
|
auth.define_tables(username=True)
|
||||||
from gluon.contrib.login_methods.saml2_auth import Saml2Auth
|
from gluon.contrib.login_methods.saml2_auth import Saml2Auth
|
||||||
|
import os
|
||||||
auth.settings.login_form=Saml2Auth(
|
auth.settings.login_form=Saml2Auth(
|
||||||
config_file = os.path.join(request.folder,'private','sp_conf'),
|
config_file = os.path.join(request.folder,'private','sp_conf'),
|
||||||
maps=dict(
|
maps=dict(
|
||||||
@@ -20,10 +21,59 @@ Include in your model (eg db.py)::
|
|||||||
email=lambda v: v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
email=lambda v: v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0],
|
||||||
user_id=lambda v: v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0]))
|
user_id=lambda v: v['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'][0]))
|
||||||
|
|
||||||
you must have private/sp_conf.py, the pysaml2 sp configuration file
|
you must have private/sp_conf.py, the pysaml2 sp configuration file. For example:
|
||||||
|
|
||||||
|
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
|
||||||
|
import os.path
|
||||||
|
import requests
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# Web2py SP url and application name
|
||||||
|
HOST = 'http://127.0.0.1:8000'
|
||||||
|
APP = 'sp'
|
||||||
|
|
||||||
|
# To load the IDP metadata...
|
||||||
|
IDP_METADATA = 'http://127.0.0.1:8088/metadata'
|
||||||
|
|
||||||
|
def full_path(local_file):
|
||||||
|
return os.path.join(BASEDIR, local_file)
|
||||||
|
|
||||||
|
CONFIG = {
|
||||||
|
# your entity id, usually your subdomain plus the url to the metadata view.
|
||||||
|
'entityid': '%s/%s/default/metadata' % (HOST, APP),
|
||||||
|
'service': {
|
||||||
|
'sp' : {
|
||||||
|
'name': 'MYSP',
|
||||||
|
'endpoints': {
|
||||||
|
'assertion_consumer_service': [
|
||||||
|
('%s/%s/default/user/login' % (HOST, APP), BINDING_HTTP_REDIRECT),
|
||||||
|
('%s/%s/default/user/login' % (HOST, APP), BINDING_HTTP_POST),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
# Your private and public key.
|
||||||
|
'key_file': full_path('pki/mykey.pem'),
|
||||||
|
'cert_file': full_path('pki/mycert.pem'),
|
||||||
|
|
||||||
|
# where the remote metadata is stored
|
||||||
|
'metadata': {
|
||||||
|
"remote": [{
|
||||||
|
"url": IDP_METADATA,
|
||||||
|
"cert":full_path('pki/mycert.pem')
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from saml2 import BINDING_HTTP_REDIRECT
|
from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
|
||||||
from saml2.client import Saml2Client
|
from saml2.client import Saml2Client
|
||||||
from gluon.utils import web2py_uuid
|
from gluon.utils import web2py_uuid
|
||||||
from gluon import current, redirect, URL
|
from gluon import current, redirect, URL
|
||||||
@@ -59,10 +109,13 @@ def saml2_handler(session, request, config_filename = None):
|
|||||||
client = Saml2Client(config_file = config_filename)
|
client = Saml2Client(config_file = config_filename)
|
||||||
idps = client.metadata.with_descriptor("idpsso")
|
idps = client.metadata.with_descriptor("idpsso")
|
||||||
entityid = idps.keys()[0]
|
entityid = idps.keys()[0]
|
||||||
bindings = [BINDING_HTTP_REDIRECT]
|
bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]
|
||||||
binding, destination = client.pick_binding(
|
binding, destination = client.pick_binding(
|
||||||
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
|
"single_sign_on_service", bindings, "idpsso", entity_id=entityid)
|
||||||
binding = BINDING_HTTP_REDIRECT
|
if request.env.request_method == 'GET':
|
||||||
|
binding = BINDING_HTTP_REDIRECT
|
||||||
|
elif request.env.request_method == 'POST':
|
||||||
|
binding = BINDING_HTTP_POST
|
||||||
if not request.vars.SAMLResponse:
|
if not request.vars.SAMLResponse:
|
||||||
req_id, req = client.create_authn_request(destination, binding=binding)
|
req_id, req = client.create_authn_request(destination, binding=binding)
|
||||||
relay_state = web2py_uuid().replace('-','')
|
relay_state = web2py_uuid().replace('-','')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Plural-Forms for fr (French))
|
# Plural-Forms for fr (French))
|
||||||
|
|
||||||
nplurals=2 # French language has 2 forms:
|
nplurals=2 # French language has 2 forms:
|
||||||
@@ -15,3 +15,55 @@ get_plural_id = lambda n: int(n != 1)
|
|||||||
# for words (or phrases) not found in plural_dict dictionary
|
# for words (or phrases) not found in plural_dict dictionary
|
||||||
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
|
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
|
||||||
|
|
||||||
|
irregular={
|
||||||
|
'aïeul': 'aïeux',
|
||||||
|
'bonhomme': 'bonshommes',
|
||||||
|
'ciel': 'cieux',
|
||||||
|
'oeil': 'yeux',
|
||||||
|
'œil': 'yeux',
|
||||||
|
'madame': 'mesdames',
|
||||||
|
'mademoiselle': 'mesdemoiselles',
|
||||||
|
'monsieur': 'messieurs',
|
||||||
|
'bijou': 'bijoux',
|
||||||
|
'caillou': 'cailloux',
|
||||||
|
'chou': 'choux',
|
||||||
|
'genou': 'genoux',
|
||||||
|
'hibou': 'hiboux',
|
||||||
|
'joujou': 'joujoux',
|
||||||
|
'pou': 'poux',
|
||||||
|
'corail': ' coraux',
|
||||||
|
'émail': 'émaux',
|
||||||
|
'travail': 'travaux',
|
||||||
|
'vitrail': 'vitraux',
|
||||||
|
'soupirail': 'soupiraux',
|
||||||
|
'bail': 'baux',
|
||||||
|
'fermail': 'fermaux',
|
||||||
|
'ventail': 'ventaux',
|
||||||
|
'bleu': 'bleus',
|
||||||
|
'pneu': 'pneus',
|
||||||
|
'émeu': 'émeus',
|
||||||
|
'enfeu': 'enfeus',
|
||||||
|
#'lieu': 'lieus', # poisson
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def construct_plural_form(word, plural_id):
|
||||||
|
u"""
|
||||||
|
>>> [construct_plural_form(x, 1) for x in \
|
||||||
|
[ 'bleu', 'nez', 'sex', 'bas', 'gruau', 'jeu', 'journal',\
|
||||||
|
'chose' ]]
|
||||||
|
['bleus', 'nez', 'sex', 'bas', 'gruaux', 'jeux', 'journaux', 'choses']
|
||||||
|
"""
|
||||||
|
if word in irregular:
|
||||||
|
return irregular[word]
|
||||||
|
if word[-1:] in ('s', 'x', 'z'):
|
||||||
|
return word
|
||||||
|
if word[-2:] in ('au', 'eu'):
|
||||||
|
return word + 'x'
|
||||||
|
if word[-2:] == 'al':
|
||||||
|
return word[0:-2] + 'aux'
|
||||||
|
return word + 's'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
doctest.testmod()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ except ImportError:
|
|||||||
|
|
||||||
class JSONRPCError(RuntimeError):
|
class JSONRPCError(RuntimeError):
|
||||||
"Error object for remote procedure call fail"
|
"Error object for remote procedure call fail"
|
||||||
def __init__(self, code, message, data=None):
|
def __init__(self, code, message, data=''):
|
||||||
value = "%s: %s\n%s" % (code, message, '\n'.join(data))
|
value = "%s: %s\n%s" % (code, message, '\n'.join(data))
|
||||||
RuntimeError.__init__(self, value)
|
RuntimeError.__init__(self, value)
|
||||||
self.code = code
|
self.code = code
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def quote(text):
|
|||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
def __init__(self, name, value, url='.', readonly=False, active=True,
|
def __init__(self, name, value, url='.', readonly=False, active=True,
|
||||||
onchange=None, **kwarg):
|
onchange=None, select=False, size=4, **kwarg):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.name = name
|
self.name = name
|
||||||
self.value = str(value)
|
self.value = str(value)
|
||||||
@@ -26,11 +26,21 @@ class Node:
|
|||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
self.active = active
|
self.active = active
|
||||||
self.onchange = onchange
|
self.onchange = onchange
|
||||||
self.size = 4
|
self.size = size
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
self.select = value if select and not isinstance(value, str) else False
|
||||||
|
|
||||||
def xml(self):
|
def xml(self):
|
||||||
return """<input name="%s" id="%s" value="%s" size="%s"
|
if self.select:
|
||||||
|
selectAttributes = dict(_name=self.name,_id=self.name,_size=self.size,
|
||||||
|
_onblur="ajax('%s/blur',['%s']);"%(self.url,self.name))
|
||||||
|
# _onkeyup="ajax('%s/keyup',['%s'], ':eval');"%(self.url,self.name),
|
||||||
|
# _onfocus="ajax('%s/focus',['%s'], ':eval');"%(self.url,self.name),
|
||||||
|
for k,v in selectAttributes.items():
|
||||||
|
self.select[k] = v
|
||||||
|
return self.select.xml()
|
||||||
|
else:
|
||||||
|
return """<input name="%s" id="%s" value="%s" size="%s"
|
||||||
onkeyup="ajax('%s/keyup',['%s'], ':eval');"
|
onkeyup="ajax('%s/keyup',['%s'], ':eval');"
|
||||||
onfocus="ajax('%s/focus',['%s'], ':eval');"
|
onfocus="ajax('%s/focus',['%s'], ':eval');"
|
||||||
onblur="ajax('%s/blur',['%s'], ':eval');" %s/>
|
onblur="ajax('%s/blur',['%s'], ':eval');" %s/>
|
||||||
@@ -391,7 +401,8 @@ class Sheet:
|
|||||||
|
|
||||||
def __init__(self, rows, cols, url='.', readonly=False,
|
def __init__(self, rows, cols, url='.', readonly=False,
|
||||||
active=True, onchange=None, value=None, data=None,
|
active=True, onchange=None, value=None, data=None,
|
||||||
headers=None, update_button="", **kwarg):
|
headers=None, update_button="", c_headers=None,
|
||||||
|
r_headers=None, **kwarg):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -425,6 +436,9 @@ class Sheet:
|
|||||||
self.tr_attributes = {}
|
self.tr_attributes = {}
|
||||||
self.td_attributes = {}
|
self.td_attributes = {}
|
||||||
|
|
||||||
|
self.c_headers = c_headers
|
||||||
|
self.r_headers = r_headers
|
||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
self.readonly = readonly
|
self.readonly = readonly
|
||||||
|
|
||||||
@@ -505,7 +519,7 @@ class Sheet:
|
|||||||
self.environment[name] = obj
|
self.environment[name] = obj
|
||||||
|
|
||||||
def cell(self, key, value, readonly=False, active=True,
|
def cell(self, key, value, readonly=False, active=True,
|
||||||
onchange=None, **kwarg):
|
onchange=None, select=False, **kwarg):
|
||||||
"""
|
"""
|
||||||
key is the name of the cell
|
key is the name of the cell
|
||||||
value is the initial value of the cell. It can be a formula "=1+3"
|
value is the initial value of the cell. It can be a formula "=1+3"
|
||||||
@@ -528,7 +542,7 @@ class Sheet:
|
|||||||
value = value(r, c)
|
value = value(r, c)
|
||||||
|
|
||||||
node = Node(key, value, self.url, readonly, active,
|
node = Node(key, value, self.url, readonly, active,
|
||||||
onchange, **kwarg)
|
onchange, select=select, **kwarg)
|
||||||
self.nodes[key] = node
|
self.nodes[key] = node
|
||||||
self[key] = value
|
self[key] = value
|
||||||
|
|
||||||
@@ -781,11 +795,19 @@ class Sheet:
|
|||||||
gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT)
|
gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT)
|
||||||
regex = re.compile('r\d+c\d+')
|
regex = re.compile('r\d+c\d+')
|
||||||
|
|
||||||
header = TR(TH(), *[TH('c%s' % c)
|
if not self.c_headers:
|
||||||
|
header = TR(TH(), *[TH('c%s' % c)
|
||||||
for c in range(self.cols)])
|
for c in range(self.cols)])
|
||||||
|
else:
|
||||||
|
header = TR(TH(), *[TH('%s' % c)
|
||||||
|
for c in self.c_headers])
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for r in range(self.rows):
|
for r in range(self.rows):
|
||||||
tds = [TH('r%s' % r), ]
|
if not self.r_headers:
|
||||||
|
tds = [TH('r%s' % r), ]
|
||||||
|
else:
|
||||||
|
tds = [TH('%s' % self.r_headers[r]), ]
|
||||||
for c in range(self.cols):
|
for c in range(self.cols):
|
||||||
key = 'r%sc%s' % (r, c)
|
key = 'r%sc%s' % (r, c)
|
||||||
attributes = {"_class": "w2p_spreadsheet_col_%s" %
|
attributes = {"_class": "w2p_spreadsheet_col_%s" %
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ class TokenHandler(tornado.web.RequestHandler):
|
|||||||
|
|
||||||
class DistributeHandler(tornado.websocket.WebSocketHandler):
|
class DistributeHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
|
||||||
def check_origin(self, origin):
|
def check_origin(self, origin):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def open(self, params):
|
def open(self, params):
|
||||||
group, token, name = params.split('/') + [None, None]
|
group, token, name = params.split('/') + [None, None]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class CustomImportException(ImportError):
|
|||||||
|
|
||||||
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
||||||
"""
|
"""
|
||||||
web2py's custom importer. It behaves like the standard Python importer but
|
web2py's custom importer. It behaves like the standard Python importer but
|
||||||
it tries to transform import statements as something like
|
it tries to transform import statements as something like
|
||||||
"import applications.app_name.modules.x".
|
"import applications.app_name.modules.x".
|
||||||
If the import fails, it falls back on naive_importer
|
If the import fails, it falls back on naive_importer
|
||||||
@@ -80,7 +80,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
|
|||||||
if not fromlist:
|
if not fromlist:
|
||||||
# import like "import x" or "import x.y"
|
# import like "import x" or "import x.y"
|
||||||
result = None
|
result = None
|
||||||
for itemname in name.split("."):
|
for itemname in name.split("."):
|
||||||
new_mod = base_importer(
|
new_mod = base_importer(
|
||||||
modules_prefix, globals, locals, [itemname], level)
|
modules_prefix, globals, locals, [itemname], level)
|
||||||
try:
|
try:
|
||||||
|
|||||||
+407
@@ -0,0 +1,407 @@
|
|||||||
|
import cgi
|
||||||
|
import copy_reg
|
||||||
|
from gluon import current, URL, DAL
|
||||||
|
from gluon.storage import Storage
|
||||||
|
from gluon.utils import web2py_uuid
|
||||||
|
from gluon.sanitizer import sanitize
|
||||||
|
|
||||||
|
# ################################################################
|
||||||
|
# New HTML Helpers
|
||||||
|
# ################################################################
|
||||||
|
|
||||||
|
def xmlescape(text):
|
||||||
|
return cgi.escape(text, True).replace("'", "'")
|
||||||
|
|
||||||
|
class TAG(object):
|
||||||
|
|
||||||
|
def __init__(self, name, *children, **attributes):
|
||||||
|
self.name = name
|
||||||
|
self.children = list(children)
|
||||||
|
self.attributes = attributes
|
||||||
|
for child in self.children:
|
||||||
|
if isinstance(child, TAG):
|
||||||
|
child.parent = self
|
||||||
|
|
||||||
|
def xml(self):
|
||||||
|
name = self.name
|
||||||
|
a = ' '.join('%s="%s"' %
|
||||||
|
(k[1:], k[1:] if v is True else xmlescape(unicode(v)))
|
||||||
|
for k,v in self.attributes.iteritems()
|
||||||
|
if k.startswith('_') and not v in (False,None))
|
||||||
|
if a:
|
||||||
|
a = ' '+a
|
||||||
|
if name.endswith('/'):
|
||||||
|
return '<%s%s/>' % (name, a)
|
||||||
|
else:
|
||||||
|
b = ''.join(s.xml() if isinstance(s,TAG) else xmlescape(unicode(s))
|
||||||
|
for s in self.children)
|
||||||
|
return '<%s%s>%s</%s>' %(name, a, b, name)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.xml()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.xml().encode('utf8')
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, int):
|
||||||
|
return self.children[key]
|
||||||
|
else:
|
||||||
|
return self.attributes[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if isinstance(key, int):
|
||||||
|
self.children[key] = value
|
||||||
|
else:
|
||||||
|
self.attributes[key] = value
|
||||||
|
|
||||||
|
def append(self, value):
|
||||||
|
self.children.append(value)
|
||||||
|
|
||||||
|
def __delitem__(self,key):
|
||||||
|
if isinstance(key, int):
|
||||||
|
self.children = self.children[:key]+self.children[key+1:]
|
||||||
|
else:
|
||||||
|
del self.attributes[key]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.children)
|
||||||
|
|
||||||
|
def find(self, query):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class METATAG(object):
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return self(name)
|
||||||
|
|
||||||
|
def __call__(self, name):
|
||||||
|
return lambda *children, **attributes: TAG(name, *children, **attributes)
|
||||||
|
|
||||||
|
tag = METATAG()
|
||||||
|
DIV = tag('div')
|
||||||
|
SPAN = tag('span')
|
||||||
|
LI = tag('li')
|
||||||
|
OL = tag('ol')
|
||||||
|
UL = tag('ul')
|
||||||
|
A = tag('a')
|
||||||
|
H1 = tag('h1')
|
||||||
|
H2 = tag('h2')
|
||||||
|
H3 = tag('h3')
|
||||||
|
H4 = tag('h4')
|
||||||
|
H5 = tag('h5')
|
||||||
|
H6 = tag('h6')
|
||||||
|
EM = tag('em')
|
||||||
|
TR = tag('tr')
|
||||||
|
TD = tag('td')
|
||||||
|
TH = tag('th')
|
||||||
|
IMG = tag('img/')
|
||||||
|
FORM = tag('form')
|
||||||
|
HEAD = tag('head')
|
||||||
|
BODY = tag('body')
|
||||||
|
TABLE = tag('table')
|
||||||
|
INPUT = tag('input/')
|
||||||
|
LABEL = tag('label')
|
||||||
|
STRONG = tag('strong')
|
||||||
|
SELECT = tag('select')
|
||||||
|
OPTION = tag('option')
|
||||||
|
TEXTAREA = tag('textarea')
|
||||||
|
|
||||||
|
# ################################################################
|
||||||
|
# New XML Helpers
|
||||||
|
# ################################################################
|
||||||
|
|
||||||
|
class XML(TAG):
|
||||||
|
"""
|
||||||
|
use it to wrap a string that contains XML/HTML so that it will not be
|
||||||
|
escaped by the template
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
>>> XML('<h1>Hello</h1>').xml()
|
||||||
|
'<h1>Hello</h1>'
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
text,
|
||||||
|
sanitize=False,
|
||||||
|
permitted_tags=[
|
||||||
|
'a','b','blockquote','br/','i','li','ol','ul','p','cite',
|
||||||
|
'code','pre','img/','h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||||
|
'table', 'tr', 'td', 'div','strong', 'span'],
|
||||||
|
allowed_attributes={
|
||||||
|
'a': ['href', 'title', 'target'],
|
||||||
|
'img': ['src', 'alt'],
|
||||||
|
'blockquote': ['type'],
|
||||||
|
'td': ['colspan']},
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
text: the XML text
|
||||||
|
sanitize: sanitize text using the permitted tags and allowed
|
||||||
|
attributes (default False)
|
||||||
|
permitted_tags: list of permitted tags (default: simple list of
|
||||||
|
tags)
|
||||||
|
allowed_attributes: dictionary of allowed attributed (default
|
||||||
|
for A, IMG and BlockQuote).
|
||||||
|
The key is the tag; the value is a list of allowed attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sanitize:
|
||||||
|
text = sanitize(text, permitted_tags, allowed_attributes)
|
||||||
|
if isinstance(text, unicode):
|
||||||
|
text = text.encode('utf8', 'xmlcharrefreplace')
|
||||||
|
elif not isinstance(text, str):
|
||||||
|
text = str(text)
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def xml(self):
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return '%s%s' % (self, other)
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return '%s%s' % (other, self)
|
||||||
|
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(str(self), str(other))
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(str(self))
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
return str(self)[i]
|
||||||
|
|
||||||
|
def __getslice__(self, i, j):
|
||||||
|
return str(self)[i:j]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for c in str(self):
|
||||||
|
yield c
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(str(self))
|
||||||
|
|
||||||
|
def XML_unpickle(data):
|
||||||
|
return XML(marshal.loads(data))
|
||||||
|
|
||||||
|
def XML_pickle(data):
|
||||||
|
return XML_unpickle, (marshal.dumps(str(data)),)
|
||||||
|
copy_reg.pickle(XML, XML_pickle, XML_unpickle)
|
||||||
|
|
||||||
|
# ################################################################
|
||||||
|
# Simple Form Style Function (example for more complex styles)
|
||||||
|
# ################################################################
|
||||||
|
|
||||||
|
def FormStyleDefault(table, vars, errors, readonly, deletable):
|
||||||
|
|
||||||
|
form = FORM(TABLE(),_method='POST',_action='#',_enctype='multipart/form-data')
|
||||||
|
for field in table:
|
||||||
|
|
||||||
|
input_id = '%s_%s' % (field.tablename, field.name)
|
||||||
|
value = field.formatter(vars.get(field.name))
|
||||||
|
error = errors.get(field.name)
|
||||||
|
field_class = field.type.split()[0].replace(':','-')
|
||||||
|
|
||||||
|
if field.type == 'blob': # never display blobs (mistake?)
|
||||||
|
continue
|
||||||
|
elif readonly or field.type=='id':
|
||||||
|
if not field.readable:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
control = field.represent and field.represent(value) or value or ''
|
||||||
|
elif not field.writable:
|
||||||
|
continue
|
||||||
|
elif field.widget:
|
||||||
|
control = field.widget(table, value)
|
||||||
|
elif field.type == 'text':
|
||||||
|
control = TEXTAREA(value or '', _id=input_id,_name=field.name)
|
||||||
|
elif field.type == 'boolean':
|
||||||
|
control = INPUT(_type='checkbox', _id=input_id, _name=field.name,
|
||||||
|
_value='ON', _checked = value)
|
||||||
|
elif field.type == 'upload':
|
||||||
|
control = DIV(INPUT(_type='file', _id=input_id, _name=field.name))
|
||||||
|
if value:
|
||||||
|
control.append(A('download',
|
||||||
|
_href=URL('default','download',args=value)))
|
||||||
|
control.append(INPUT(_type='checkbox',_value='ON',
|
||||||
|
_name='_delete_'+field.name))
|
||||||
|
control.append('(check to remove)')
|
||||||
|
elif hasattr(field.requires, 'options'):
|
||||||
|
multiple = field.type.startswith('list:')
|
||||||
|
value = value if isinstance(value, list) else [value]
|
||||||
|
options = [OPTION(v,_value=k,_selected=(k in value))
|
||||||
|
for k,v in field.requires.options()]
|
||||||
|
control = SELECT(*options, _id=input_id, _name=field.name,
|
||||||
|
_multiple=multiple)
|
||||||
|
else:
|
||||||
|
field_type = 'password' if field.type == 'password' else 'text'
|
||||||
|
control = INPUT(_type=field_type, _id=input_id, _name=field.name,
|
||||||
|
_value=value, _class=field_class)
|
||||||
|
|
||||||
|
form[0].append(TR(TD(LABEL(field.label,_for=input_id)),
|
||||||
|
TD(control,DIV(error,_class='error') if error else ''),
|
||||||
|
TD(field.comment or '')))
|
||||||
|
|
||||||
|
td = TD(INPUT(_type='submit',_value='Submit'))
|
||||||
|
if deletable:
|
||||||
|
td.append(INPUT(_type='checkbox',_value='ON',_name='_delete'))
|
||||||
|
td.append('(check to delete)')
|
||||||
|
form[0].append(TR(TD(),td,TD()))
|
||||||
|
return form
|
||||||
|
|
||||||
|
# ################################################################
|
||||||
|
# Form object (replaced SQLFORM)
|
||||||
|
# ################################################################
|
||||||
|
|
||||||
|
class Form(object):
|
||||||
|
"""
|
||||||
|
Usage in web2py controller:
|
||||||
|
|
||||||
|
def index():
|
||||||
|
form = Form(db.thing, record=1)
|
||||||
|
if form.accepted: ...
|
||||||
|
elif form.errors: ...
|
||||||
|
else: ...
|
||||||
|
return dict(form=form)
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- table: a DAL table or a list of fields (equivalent to old SQLFORM.factory)
|
||||||
|
- record: a DAL record or record id
|
||||||
|
- readonly: set to True to make a readonly form
|
||||||
|
- deletable: set to False to disallow deletion of record
|
||||||
|
- formstyle: a function that renders the form using helpers (FormStyleDefault)
|
||||||
|
- dbio: set to False to prevent any DB write
|
||||||
|
- keepvalues: (NOT IMPLEMENTED)
|
||||||
|
- formname: the optional name of this form
|
||||||
|
- csrf: set to False to disable CRSF protection
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
table,
|
||||||
|
record=None,
|
||||||
|
readonly=False,
|
||||||
|
deletable=True,
|
||||||
|
formstyle=FormStyleDefault,
|
||||||
|
dbio=True,
|
||||||
|
keepvalues=False,
|
||||||
|
formname=False,
|
||||||
|
csrf=True):
|
||||||
|
|
||||||
|
if isinstance(table, list):
|
||||||
|
dbio = False
|
||||||
|
# mimic a table from a list of fields without calling define_table
|
||||||
|
formname = formname or 'none'
|
||||||
|
for field in table: field.tablename = formname
|
||||||
|
|
||||||
|
if isinstance(record, (int, long, basestring)):
|
||||||
|
record_id = int(str(record))
|
||||||
|
self.record = table[record_id]
|
||||||
|
else:
|
||||||
|
self.record = record
|
||||||
|
|
||||||
|
self.table = table
|
||||||
|
self.readonly = readonly
|
||||||
|
self.deletable = deletable and not readonly and self.record
|
||||||
|
self.formstyle = formstyle
|
||||||
|
self.dbio = dbio
|
||||||
|
self.keepvalues = True if keepvalues or self.record else False
|
||||||
|
self.csrf = csrf
|
||||||
|
self.vars = Storage()
|
||||||
|
self.errors = Storage()
|
||||||
|
self.submitted = False
|
||||||
|
self.deleted = False
|
||||||
|
self.accepted = False
|
||||||
|
self.cached_helper = False
|
||||||
|
self.formname = formname or table._tablename
|
||||||
|
self.formkey = None
|
||||||
|
|
||||||
|
request = current.request
|
||||||
|
session = current.session
|
||||||
|
post_vars = request.post_vars
|
||||||
|
|
||||||
|
if readonly or request.env.request_method=='GET':
|
||||||
|
if self.record:
|
||||||
|
self.vars = self.record
|
||||||
|
else:
|
||||||
|
print post_vars
|
||||||
|
self.submitted = True
|
||||||
|
# check for CSRF
|
||||||
|
if csrf and self.formname in (session._formkeys or {}):
|
||||||
|
self.formkey = session._formkeys[self.formname]
|
||||||
|
# validate fields
|
||||||
|
if not csrf or post_vars._formkey == self.formkey:
|
||||||
|
if not post_vars._delete:
|
||||||
|
for field in self.table:
|
||||||
|
if field.writable:
|
||||||
|
value = post_vars.get(field.name)
|
||||||
|
(value, error) = field.validate(value)
|
||||||
|
if field.type == 'upload':
|
||||||
|
delete = post_vars.get('_delete_'+field.name)
|
||||||
|
if value is not None and hasattr(value,'file'):
|
||||||
|
value = field.store(value.file,
|
||||||
|
value.filename,
|
||||||
|
field.uploadfolder)
|
||||||
|
elif self.record and not delete:
|
||||||
|
value = self.record.get(field.name)
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
self.vars[field.name] = value
|
||||||
|
if error:
|
||||||
|
self.errors[field.name] = error
|
||||||
|
if self.record:
|
||||||
|
self.vars.id = self.record.id
|
||||||
|
if not self.errors:
|
||||||
|
self.accepted = True
|
||||||
|
if dbio:
|
||||||
|
if self.record:
|
||||||
|
self.record.update_record(**self.vars)
|
||||||
|
else:
|
||||||
|
# warning, should we really insert if record
|
||||||
|
self.vars.id = self.table.insert(**self.vars)
|
||||||
|
elif dbio:
|
||||||
|
self.deleted = True
|
||||||
|
self.record.delete_record()
|
||||||
|
# store key for future CSRF
|
||||||
|
if csrf:
|
||||||
|
if not session._formkeys:
|
||||||
|
session._formkeys = {}
|
||||||
|
if self.formname not in session._formkeys:
|
||||||
|
session._formkeys[self.formname] = web2py_uuid()
|
||||||
|
self.formkey = session._formkeys[self.formname]
|
||||||
|
|
||||||
|
def clear():
|
||||||
|
self.vars.clear()
|
||||||
|
self.errors.clear()
|
||||||
|
for field in self.table:
|
||||||
|
self.vars[field.name] = field.default
|
||||||
|
|
||||||
|
def helper(self):
|
||||||
|
if not self.cached_helper:
|
||||||
|
cached_helper = self.formstyle(self.table,
|
||||||
|
self.vars,
|
||||||
|
self.errors,
|
||||||
|
self.readonly,
|
||||||
|
self.deletable)
|
||||||
|
if self.csrf:
|
||||||
|
cached_helper.append(INPUT(_type='hidden',_name='_formkey',
|
||||||
|
_value=self.formkey))
|
||||||
|
self.cached_helper = cached_helper
|
||||||
|
return cached_helper
|
||||||
|
|
||||||
|
def xml(self):
|
||||||
|
return self.helper().xml()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.xml()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.xml().encode('utf8')
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
print(DIV(SPAN('this',STRONG('a test'),XML('1<2')),_id=1,_class="my class"))
|
||||||
+11
-4
@@ -208,7 +208,7 @@ class Request(Storage):
|
|||||||
def parse_get_vars(self):
|
def parse_get_vars(self):
|
||||||
"""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 get_vars.iteritems():
|
for (key, value) in get_vars.iteritems():
|
||||||
@@ -1023,10 +1023,16 @@ class Session(Storage):
|
|||||||
def _fixup_before_save(self):
|
def _fixup_before_save(self):
|
||||||
response = current.response
|
response = current.response
|
||||||
rcookies = response.cookies
|
rcookies = response.cookies
|
||||||
if self._forget and response.session_id_name in rcookies:
|
scookies = rcookies.get(response.session_id_name)
|
||||||
|
if not scookies:
|
||||||
|
return
|
||||||
|
if self._forget:
|
||||||
del rcookies[response.session_id_name]
|
del rcookies[response.session_id_name]
|
||||||
elif self._secure and response.session_id_name in rcookies:
|
return
|
||||||
rcookies[response.session_id_name]['secure'] = True
|
if self.get('httponly_cookies',True):
|
||||||
|
scookies['HttpOnly'] = True
|
||||||
|
if self._secure:
|
||||||
|
scookies['secure'] = True
|
||||||
|
|
||||||
def clear_session_cookies(self):
|
def clear_session_cookies(self):
|
||||||
request = current.request
|
request = current.request
|
||||||
@@ -1074,6 +1080,7 @@ class Session(Storage):
|
|||||||
if response.session_storage_type == 'file':
|
if response.session_storage_type == 'file':
|
||||||
target = recfile.generate(response.session_filename)
|
target = recfile.generate(response.session_filename)
|
||||||
try:
|
try:
|
||||||
|
self._close(response)
|
||||||
os.unlink(target)
|
os.unlink(target)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
+8
-3
@@ -668,7 +668,7 @@ class XML(XmlComponent):
|
|||||||
|
|
||||||
|
|
||||||
def XML_unpickle(data):
|
def XML_unpickle(data):
|
||||||
return marshal.loads(data)
|
return XML(marshal.loads(data))
|
||||||
|
|
||||||
|
|
||||||
def XML_pickle(data):
|
def XML_pickle(data):
|
||||||
@@ -784,6 +784,9 @@ class DIV(XmlComponent):
|
|||||||
else:
|
else:
|
||||||
return self.components[i]
|
return self.components[i]
|
||||||
|
|
||||||
|
def get(self, i):
|
||||||
|
return self.attributes.get(i)
|
||||||
|
|
||||||
def __setitem__(self, i, value):
|
def __setitem__(self, i, value):
|
||||||
"""
|
"""
|
||||||
Sets attribute with name 'i' or component #i.
|
Sets attribute with name 'i' or component #i.
|
||||||
@@ -1135,7 +1138,7 @@ class DIV(XmlComponent):
|
|||||||
for (key, value) in kargs.iteritems():
|
for (key, value) in kargs.iteritems():
|
||||||
if key not in ['first_only', 'replace', 'find_text']:
|
if key not in ['first_only', 'replace', 'find_text']:
|
||||||
if isinstance(value, (str, int)):
|
if isinstance(value, (str, int)):
|
||||||
if self[key] != str(value):
|
if str(self[key]) != str(value):
|
||||||
check = False
|
check = False
|
||||||
elif key in self.attributes:
|
elif key in self.attributes:
|
||||||
if not value.search(str(self[key])):
|
if not value.search(str(self[key])):
|
||||||
@@ -1856,6 +1859,8 @@ class INPUT(DIV):
|
|||||||
try:
|
try:
|
||||||
(value, errors) = validator(value)
|
(value, errors) = validator(value)
|
||||||
except:
|
except:
|
||||||
|
import traceback
|
||||||
|
print traceback.format_exc()
|
||||||
msg = "Validation error, field:%s %s" % (name,validator)
|
msg = "Validation error, field:%s %s" % (name,validator)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
if not errors is None:
|
if not errors is None:
|
||||||
@@ -2643,7 +2648,7 @@ def test():
|
|||||||
>>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
|
>>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
|
||||||
>>> isinstance(form.as_dict(), dict)
|
>>> isinstance(form.as_dict(), dict)
|
||||||
True
|
True
|
||||||
>>> form.as_dict(flat=True).has_key("vars")
|
>>> "vars" in form.as_dict(flat=True)
|
||||||
True
|
True
|
||||||
>>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
|
>>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
|
||||||
True
|
True
|
||||||
|
|||||||
+9
-6
@@ -370,8 +370,8 @@ def wsgibase(environ, responder):
|
|||||||
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 = cmd_opts and cmd_opts.shell,
|
is_shell = False,
|
||||||
is_sheduler = cmd_opts and cmd_opts.scheduler,
|
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 \
|
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
|
||||||
or env.https == 'on'
|
or env.https == 'on'
|
||||||
@@ -423,10 +423,13 @@ def wsgibase(environ, responder):
|
|||||||
# ##################################################
|
# ##################################################
|
||||||
|
|
||||||
if env.http_cookie:
|
if env.http_cookie:
|
||||||
try:
|
for single_cookie in env.http_cookie.split(';'):
|
||||||
request.cookies.load(env.http_cookie)
|
single_cookie = single_cookie.strip()
|
||||||
except Cookie.CookieError, e:
|
if single_cookie:
|
||||||
pass # invalid cookies
|
try:
|
||||||
|
request.cookies.load(single_cookie)
|
||||||
|
except Cookie.CookieError:
|
||||||
|
pass # single invalid cookie ignore
|
||||||
|
|
||||||
# ##################################################
|
# ##################################################
|
||||||
# try load session or create new session file
|
# try load session or create new session file
|
||||||
|
|||||||
+1
-1
Submodule gluon/packages/dal updated: 62eb7767db...dcfb5f58aa
@@ -129,6 +129,8 @@ def env(
|
|||||||
if global_settings.cmd_options:
|
if global_settings.cmd_options:
|
||||||
ip = global_settings.cmd_options.ip
|
ip = global_settings.cmd_options.ip
|
||||||
port = global_settings.cmd_options.port
|
port = global_settings.cmd_options.port
|
||||||
|
request.is_shell = global_settings.cmd_options.shell is not None
|
||||||
|
request.is_scheduler = global_settings.cmd_options.scheduler is not None
|
||||||
else:
|
else:
|
||||||
ip, port = '127.0.0.1', '8000'
|
ip, port = '127.0.0.1', '8000'
|
||||||
request.env.http_host = '%s:%s' % (ip, port)
|
request.env.http_host = '%s:%s' % (ip, port)
|
||||||
|
|||||||
+37
-23
@@ -646,13 +646,12 @@ class AutocompleteWidget(object):
|
|||||||
def __init__(self, request, field, id_field=None, db=None,
|
def __init__(self, request, field, id_field=None, db=None,
|
||||||
orderby=None, limitby=(0, 10), distinct=False,
|
orderby=None, limitby=(0, 10), distinct=False,
|
||||||
keyword='_autocomplete_%(tablename)s_%(fieldname)s',
|
keyword='_autocomplete_%(tablename)s_%(fieldname)s',
|
||||||
min_length=2, help_fields=None, help_string=None):
|
min_length=2, help_fields=None, help_string=None, at_beginning = True):
|
||||||
|
|
||||||
self.help_fields = help_fields or []
|
self.help_fields = help_fields or []
|
||||||
self.help_string = help_string
|
self.help_string = help_string
|
||||||
if self.help_fields and not self.help_string:
|
if self.help_fields and not self.help_string:
|
||||||
self.help_string = ' '.join('%%(%s)s' % f.name
|
self.help_string = ' '.join('%%(%s)s' % f.name for f in self.help_fields)
|
||||||
for f in self.help_fields)
|
|
||||||
|
|
||||||
self.request = request
|
self.request = request
|
||||||
self.keyword = keyword % dict(tablename=field.tablename,
|
self.keyword = keyword % dict(tablename=field.tablename,
|
||||||
@@ -662,6 +661,7 @@ class AutocompleteWidget(object):
|
|||||||
self.limitby = limitby
|
self.limitby = limitby
|
||||||
self.distinct = distinct
|
self.distinct = distinct
|
||||||
self.min_length = min_length
|
self.min_length = min_length
|
||||||
|
self.at_beginning = at_beginning
|
||||||
self.fields = [field]
|
self.fields = [field]
|
||||||
if id_field:
|
if id_field:
|
||||||
self.is_reference = True
|
self.is_reference = True
|
||||||
@@ -679,8 +679,10 @@ class AutocompleteWidget(object):
|
|||||||
field = self.fields[0]
|
field = self.fields[0]
|
||||||
if settings and settings.global_settings.web2py_runtime_gae:
|
if settings and settings.global_settings.web2py_runtime_gae:
|
||||||
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
|
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
|
||||||
else:
|
elif self.at_beginning:
|
||||||
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
|
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
|
||||||
|
else:
|
||||||
|
rows = self.db(field.contains(self.request.vars[self.keyword], case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
|
||||||
if rows:
|
if rows:
|
||||||
if self.is_reference:
|
if self.is_reference:
|
||||||
id_field = self.fields[1]
|
id_field = self.fields[1]
|
||||||
@@ -734,7 +736,7 @@ class AutocompleteWidget(object):
|
|||||||
name=name, div_id=div_id, u='F' + self.keyword)
|
name=name, div_id=div_id, u='F' + self.keyword)
|
||||||
if self.min_length == 0:
|
if self.min_length == 0:
|
||||||
attr['_onfocus'] = attr['_onkeyup']
|
attr['_onfocus'] = attr['_onkeyup']
|
||||||
return CAT(INPUT(**attr),
|
return CAT(INPUT(**attr),
|
||||||
INPUT(_type='hidden', _id=key3, _value=value,
|
INPUT(_type='hidden', _id=key3, _value=value,
|
||||||
_name=name, requires=field.requires),
|
_name=name, requires=field.requires),
|
||||||
DIV(_id=div_id, _style='position:absolute;'))
|
DIV(_id=div_id, _style='position:absolute;'))
|
||||||
@@ -747,7 +749,7 @@ class AutocompleteWidget(object):
|
|||||||
key=self.keyword, id=attr['_id'], div_id=div_id, u='F' + self.keyword)
|
key=self.keyword, id=attr['_id'], div_id=div_id, u='F' + self.keyword)
|
||||||
if self.min_length == 0:
|
if self.min_length == 0:
|
||||||
attr['_onfocus'] = attr['_onkeyup']
|
attr['_onfocus'] = attr['_onkeyup']
|
||||||
return CAT(INPUT(**attr),
|
return CAT(INPUT(**attr),
|
||||||
DIV(_id=div_id, _style='position:absolute;'))
|
DIV(_id=div_id, _style='position:absolute;'))
|
||||||
|
|
||||||
|
|
||||||
@@ -838,7 +840,7 @@ def formstyle_bootstrap(form, fields):
|
|||||||
controls.add_class('span4')
|
controls.add_class('span4')
|
||||||
|
|
||||||
if isinstance(label, LABEL):
|
if isinstance(label, LABEL):
|
||||||
label['_class'] = 'control-label'
|
label['_class'] = add_class(label.get('_class'),'control-label')
|
||||||
|
|
||||||
if _submit:
|
if _submit:
|
||||||
# submit button has unwrapped label and controls, different class
|
# submit button has unwrapped label and controls, different class
|
||||||
@@ -888,7 +890,7 @@ def formstyle_bootstrap3_stacked(form, fields):
|
|||||||
e.add_class('form-control')
|
e.add_class('form-control')
|
||||||
|
|
||||||
if isinstance(label, LABEL):
|
if isinstance(label, LABEL):
|
||||||
label['_class'] = 'control-label'
|
label['_class'] = add_class(label.get('_class'),'control-label')
|
||||||
|
|
||||||
parent.append(DIV(label, _controls, _class='form-group', _id=id))
|
parent.append(DIV(label, _controls, _class='form-group', _id=id))
|
||||||
return parent
|
return parent
|
||||||
@@ -936,8 +938,10 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
|
|||||||
elif isinstance(controls, UL):
|
elif isinstance(controls, UL):
|
||||||
for e in controls.elements("input"):
|
for e in controls.elements("input"):
|
||||||
e.add_class('form-control')
|
e.add_class('form-control')
|
||||||
|
elif controls is None or isinstance(controls, basestring):
|
||||||
|
_controls = P(controls, _class="form-control-static %s" % col_class)
|
||||||
if isinstance(label, LABEL):
|
if isinstance(label, LABEL):
|
||||||
label['_class'] = 'control-label %s' % label_col_class
|
label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class)
|
||||||
|
|
||||||
parent.append(DIV(label, _controls, _class='form-group', _id=id))
|
parent.append(DIV(label, _controls, _class='form-group', _id=id))
|
||||||
return parent
|
return parent
|
||||||
@@ -1120,10 +1124,12 @@ class SQLFORM(FORM):
|
|||||||
raise HTTP(404, "Object not found")
|
raise HTTP(404, "Object not found")
|
||||||
self.record = record
|
self.record = record
|
||||||
|
|
||||||
self.record_id = record_id
|
|
||||||
if keyed:
|
if keyed:
|
||||||
self.record_id = dict([(k, record and str(record[k]) or None)
|
self.record_id = dict([(k, record and str(record[k]) or None)
|
||||||
for k in table._primarykey])
|
for k in table._primarykey])
|
||||||
|
else:
|
||||||
|
self.record_id = record_id
|
||||||
|
|
||||||
self.field_parent = {}
|
self.field_parent = {}
|
||||||
xfields = []
|
xfields = []
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
@@ -1181,6 +1187,14 @@ class SQLFORM(FORM):
|
|||||||
label = LABEL(label, label and sep, _for=field_id,
|
label = LABEL(label, label and sep, _for=field_id,
|
||||||
_id=field_id + SQLFORM.ID_LABEL_SUFFIX)
|
_id=field_id + SQLFORM.ID_LABEL_SUFFIX)
|
||||||
|
|
||||||
|
cond = readonly or \
|
||||||
|
(not ignore_rw and not field.writable and field.readable)
|
||||||
|
|
||||||
|
if cond:
|
||||||
|
label['_class'] = 'readonly'
|
||||||
|
else:
|
||||||
|
label['_class'] = ''
|
||||||
|
|
||||||
row_id = field_id + SQLFORM.ID_ROW_SUFFIX
|
row_id = field_id + SQLFORM.ID_ROW_SUFFIX
|
||||||
if field.type == 'id':
|
if field.type == 'id':
|
||||||
self.custom.dspval.id = nbsp
|
self.custom.dspval.id = nbsp
|
||||||
@@ -1209,8 +1223,6 @@ class SQLFORM(FORM):
|
|||||||
default = field.default
|
default = field.default
|
||||||
if isinstance(default, CALLABLETYPES):
|
if isinstance(default, CALLABLETYPES):
|
||||||
default = default()
|
default = default()
|
||||||
cond = readonly or \
|
|
||||||
(not ignore_rw and not field.writable and field.readable)
|
|
||||||
|
|
||||||
if default is not None and not cond:
|
if default is not None and not cond:
|
||||||
default = field.formatter(default)
|
default = field.formatter(default)
|
||||||
@@ -1492,13 +1504,12 @@ class SQLFORM(FORM):
|
|||||||
hideerror=hideerror,
|
hideerror=hideerror,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
self.deleted = \
|
self.deleted = request_vars.get(self.FIELDNAME_REQUEST_DELETE, False)
|
||||||
request_vars.get(self.FIELDNAME_REQUEST_DELETE, False)
|
|
||||||
|
|
||||||
self.custom.end = CAT(self.hidden_fields(), self.custom.end)
|
self.custom.end = CAT(self.hidden_fields(), self.custom.end)
|
||||||
|
|
||||||
auch = record_id and self.errors and self.deleted
|
delete_exception = self.record_id and self.errors and self.deleted
|
||||||
|
|
||||||
if self.record_changed and self.detect_record_change:
|
if self.record_changed and self.detect_record_change:
|
||||||
message_onchange = \
|
message_onchange = \
|
||||||
@@ -1510,8 +1521,9 @@ class SQLFORM(FORM):
|
|||||||
if message_onchange is not None:
|
if message_onchange is not None:
|
||||||
current.response.flash = message_onchange
|
current.response.flash = message_onchange
|
||||||
return ret
|
return ret
|
||||||
elif (not ret) and (not auch):
|
|
||||||
# auch is true when user tries to delete a record
|
elif (not ret) and (not delete_exception):
|
||||||
|
# delete_exception is true when user tries to delete a record
|
||||||
# that does not pass validation, yet it should be deleted
|
# that does not pass validation, yet it should be deleted
|
||||||
for fieldname in self.fields:
|
for fieldname in self.fields:
|
||||||
|
|
||||||
@@ -1541,9 +1553,10 @@ class SQLFORM(FORM):
|
|||||||
self.accepted = ret
|
self.accepted = ret
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
if record_id and str(record_id) != str(self.record_id):
|
if self.record_id:
|
||||||
raise SyntaxError('user is tampering with form\'s record_id: '
|
if str(record_id) != str(self.record_id):
|
||||||
'%s != %s' % (record_id, self.record_id))
|
raise SyntaxError('user is tampering with form\'s record_id: '
|
||||||
|
'%s != %s' % (record_id, self.record_id))
|
||||||
|
|
||||||
if record_id and dbio and not keyed:
|
if record_id and dbio and not keyed:
|
||||||
self.vars.id = self.record[self.id_field_name]
|
self.vars.id = self.record[self.id_field_name]
|
||||||
@@ -1707,6 +1720,7 @@ class SQLFORM(FORM):
|
|||||||
self.id_field_name]).update(**fields)
|
self.id_field_name]).update(**fields)
|
||||||
else:
|
else:
|
||||||
self.vars.id = self.table.insert(**fields)
|
self.vars.id = self.table.insert(**fields)
|
||||||
|
|
||||||
self.accepted = ret
|
self.accepted = ret
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -2069,7 +2083,7 @@ class SQLFORM(FORM):
|
|||||||
## if it's not an integer
|
## if it's not an integer
|
||||||
if cache_count is None or isinstance(cache_count, tuple):
|
if cache_count is None or isinstance(cache_count, tuple):
|
||||||
if groupby:
|
if groupby:
|
||||||
c = 'count(*)'
|
c = 'count(*) AS count_all'
|
||||||
nrows = db.executesql(
|
nrows = db.executesql(
|
||||||
'select count(*) from (%s) _tmp;' %
|
'select count(*) from (%s) _tmp;' %
|
||||||
dbset._select(c, left=left, cacheable=True,
|
dbset._select(c, left=left, cacheable=True,
|
||||||
@@ -2104,7 +2118,7 @@ class SQLFORM(FORM):
|
|||||||
elif isinstance(orderby, Field) and orderby is not field_id:
|
elif isinstance(orderby, Field) and orderby is not field_id:
|
||||||
# here we're with an ASC order on a field stored as orderby
|
# here we're with an ASC order on a field stored as orderby
|
||||||
orderby = orderby | field_id
|
orderby = orderby | field_id
|
||||||
elif (isinstance(orderby, Expression) and
|
elif (isinstance(orderby, Expression) and
|
||||||
orderby.first and orderby.first is not field_id):
|
orderby.first and orderby.first is not field_id):
|
||||||
# here we're with a DESC order on a field stored as orderby.first
|
# here we're with a DESC order on a field stored as orderby.first
|
||||||
orderby = orderby | field_id
|
orderby = orderby | field_id
|
||||||
|
|||||||
@@ -898,6 +898,9 @@ def render(content="hello world",
|
|||||||
if not 'NOESCAPE' in context:
|
if not 'NOESCAPE' in context:
|
||||||
context['NOESCAPE'] = NOESCAPE
|
context['NOESCAPE'] = NOESCAPE
|
||||||
|
|
||||||
|
if isinstance(content, unicode):
|
||||||
|
content = content.encode('utf8')
|
||||||
|
|
||||||
# save current response class
|
# save current response class
|
||||||
if context and 'response' in context:
|
if context and 'response' in context:
|
||||||
old_response_body = context['response'].body
|
old_response_body = context['response'].body
|
||||||
|
|||||||
+69
-1
@@ -4,15 +4,16 @@
|
|||||||
Unit tests for gluon.dal
|
Unit tests for gluon.dal
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from fix_path import fix_sys_path
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
fix_sys_path(__file__)
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
|
|
||||||
from gluon.dal import DAL, Field
|
from gluon.dal import DAL, Field
|
||||||
|
|
||||||
|
|
||||||
def tearDownModule():
|
def tearDownModule():
|
||||||
try:
|
try:
|
||||||
os.unlink('dummy.db')
|
os.unlink('dummy.db')
|
||||||
@@ -50,6 +51,73 @@ class TestDefaultValidators(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_exec_for_file(filename):
|
||||||
|
module = []
|
||||||
|
if filename.endswith('.py'):
|
||||||
|
filename = filename[:-3]
|
||||||
|
elif os.path.split(filename)[1] == '__init__.py':
|
||||||
|
filename = os.path.dirname(filename)
|
||||||
|
else:
|
||||||
|
raise 'The file provided (%s) does is not a valid Python file.'
|
||||||
|
filename = os.path.realpath(filename)
|
||||||
|
dirpath = filename
|
||||||
|
while 1:
|
||||||
|
dirpath, extra = os.path.split(dirpath)
|
||||||
|
module.append(extra)
|
||||||
|
if not os.path.isfile(os.path.join(dirpath, '__init__.py')):
|
||||||
|
break
|
||||||
|
sys.path.insert(0, dirpath)
|
||||||
|
return '.'.join(module[::-1])
|
||||||
|
|
||||||
|
|
||||||
|
def load_pydal_tests_module():
|
||||||
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
if not os.path.isfile(os.path.join(path, 'web2py.py')):
|
||||||
|
i = 0
|
||||||
|
while i < 10:
|
||||||
|
i += 1
|
||||||
|
if os.path.exists(os.path.join(path, 'web2py.py')):
|
||||||
|
break
|
||||||
|
path = os.path.abspath(os.path.join(path, '..'))
|
||||||
|
pydal_test_path = os.path.join(
|
||||||
|
path, "gluon", "packages", "dal", "tests", "__init__.py")
|
||||||
|
mname = _prepare_exec_for_file(pydal_test_path)
|
||||||
|
mod = __import__(mname)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def pydal_suite():
|
||||||
|
mod = load_pydal_tests_module()
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
tlist = [
|
||||||
|
getattr(mod, el) for el in mod.__dict__.keys() if el.startswith("Test")
|
||||||
|
]
|
||||||
|
for t in tlist:
|
||||||
|
suite.addTest(unittest.makeSuite(t))
|
||||||
|
return suite
|
||||||
|
|
||||||
|
|
||||||
|
class TestDALAdapters(unittest.TestCase):
|
||||||
|
def _run_tests(self):
|
||||||
|
suite = pydal_suite()
|
||||||
|
return unittest.TextTestRunner(verbosity=2).run(suite)
|
||||||
|
|
||||||
|
def test_mysql(self):
|
||||||
|
if os.environ.get('APPVEYOR'):
|
||||||
|
return
|
||||||
|
os.environ["DB"] = "mysql://root:@localhost/pydal"
|
||||||
|
result = self._run_tests()
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
def test_pg8000(self):
|
||||||
|
if os.environ.get('APPVEYOR'):
|
||||||
|
return
|
||||||
|
os.environ["DB"] = "postgres:pg8000://postgres:@localhost/pydal"
|
||||||
|
result = self._run_tests()
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
tearDownModule()
|
tearDownModule()
|
||||||
|
|||||||
@@ -6,17 +6,43 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
from fix_path import fix_sys_path
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
fix_sys_path(__file__)
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
from gluon.globals import Response
|
from gluon.globals import Request, Response, Session
|
||||||
from gluon import URL
|
from gluon import URL
|
||||||
|
|
||||||
|
def setup_clean_session():
|
||||||
|
request = Request(env={})
|
||||||
|
request.application = 'a'
|
||||||
|
request.controller = 'c'
|
||||||
|
request.function = 'f'
|
||||||
|
request.folder = 'applications/admin'
|
||||||
|
response = Response()
|
||||||
|
session = Session()
|
||||||
|
session.connect(request, response)
|
||||||
|
from gluon.globals import current
|
||||||
|
current.request = request
|
||||||
|
current.response = response
|
||||||
|
current.session = session
|
||||||
|
return current
|
||||||
|
|
||||||
class testResponse(unittest.TestCase):
|
class testResponse(unittest.TestCase):
|
||||||
|
|
||||||
|
#port from python 2.7, needed for 2.5 and 2.6 tests
|
||||||
|
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
||||||
|
"""Fail the test unless the text matches the regular expression."""
|
||||||
|
if isinstance(expected_regexp, basestring):
|
||||||
|
expected_regexp = re.compile(expected_regexp)
|
||||||
|
if not expected_regexp.search(text):
|
||||||
|
msg = msg or "Regexp didn't match"
|
||||||
|
msg = '%s: %r not found in %r' % (
|
||||||
|
msg, expected_regexp.pattern, text)
|
||||||
|
raise self.failureException(msg)
|
||||||
|
|
||||||
def test_include_files(self):
|
def test_include_files(self):
|
||||||
|
|
||||||
def return_includes(response, extensions=None):
|
def return_includes(response, extensions=None):
|
||||||
@@ -120,5 +146,43 @@ class testResponse(unittest.TestCase):
|
|||||||
content = return_includes(response)
|
content = return_includes(response)
|
||||||
self.assertEqual(content, '')
|
self.assertEqual(content, '')
|
||||||
|
|
||||||
|
def test_cookies(self):
|
||||||
|
current = setup_clean_session()
|
||||||
|
cookie = str(current.response.cookies)
|
||||||
|
session_key='%s=%s'%(current.response.session_id_name,current.response.session_id)
|
||||||
|
self.assertRegexpMatches(cookie, r'^Set-Cookie: ')
|
||||||
|
self.assertTrue(session_key in cookie)
|
||||||
|
self.assertTrue('Path=/' in cookie)
|
||||||
|
|
||||||
|
def test_cookies_secure(self):
|
||||||
|
current = setup_clean_session()
|
||||||
|
current.session._fixup_before_save()
|
||||||
|
cookie = str(current.response.cookies)
|
||||||
|
self.assertTrue('secure' not in cookie)
|
||||||
|
|
||||||
|
current = setup_clean_session()
|
||||||
|
current.session.secure()
|
||||||
|
current.session._fixup_before_save()
|
||||||
|
cookie = str(current.response.cookies)
|
||||||
|
self.assertTrue('secure' in cookie)
|
||||||
|
|
||||||
|
def test_cookies_httponly(self):
|
||||||
|
current = setup_clean_session()
|
||||||
|
current.session._fixup_before_save()
|
||||||
|
cookie = str(current.response.cookies)
|
||||||
|
self.assertTrue('httponly' in cookie)
|
||||||
|
|
||||||
|
current = setup_clean_session()
|
||||||
|
current.session.httponly_cookies = True
|
||||||
|
current.session._fixup_before_save()
|
||||||
|
cookie = str(current.response.cookies)
|
||||||
|
self.assertTrue('httponly' in cookie)
|
||||||
|
|
||||||
|
current = setup_clean_session()
|
||||||
|
current.session.httponly_cookies = False
|
||||||
|
current.session._fixup_before_save()
|
||||||
|
cookie = str(current.response.cookies)
|
||||||
|
self.assertTrue('httponly' not in cookie)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ class TestBareHelpers(unittest.TestCase):
|
|||||||
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
|
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
|
||||||
XML('<h1>HelloWorld</h1>'))
|
XML('<h1>HelloWorld</h1>'))
|
||||||
#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('<p>Test</p><br /><p>Test</p><br />'))
|
XML('<p>Test</p><br /><p>Test</p><br />'))
|
||||||
|
|
||||||
def testTAG(self):
|
def testTAG(self):
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class TestList(unittest.TestCase):
|
|||||||
'something')
|
'something')
|
||||||
# except if default is especified
|
# except if default is especified
|
||||||
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
|
self.assertEqual(b(0, default=0, otherwise=lambda: 'something'), 0)
|
||||||
|
|
||||||
def test_listgetitem(self):
|
def test_listgetitem(self):
|
||||||
'''Mantains list behaviour.'''
|
'''Mantains list behaviour.'''
|
||||||
a = List((1, 2, 3))
|
a = List((1, 2, 3))
|
||||||
|
|||||||
+11
-11
@@ -24,42 +24,42 @@ class TestUtils(unittest.TestCase):
|
|||||||
|
|
||||||
data = md5_hash("web2py rocks")
|
data = md5_hash("web2py rocks")
|
||||||
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
|
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
|
||||||
|
|
||||||
def test_compare(self):
|
def test_compare(self):
|
||||||
""" Tests the compare funciton """
|
""" Tests the compare funciton """
|
||||||
|
|
||||||
a, b = 'test123', 'test123'
|
a, b = 'test123', 'test123'
|
||||||
compare_result_true = compare(a, b)
|
compare_result_true = compare(a, b)
|
||||||
self.assertTrue(compare_result_true)
|
self.assertTrue(compare_result_true)
|
||||||
|
|
||||||
a, b = 'test123', 'test456'
|
a, b = 'test123', 'test456'
|
||||||
compare_result_false = compare(a, b)
|
compare_result_false = compare(a, b)
|
||||||
self.assertFalse(compare_result_false)
|
self.assertFalse(compare_result_false)
|
||||||
|
|
||||||
def test_simple_hash(self):
|
def test_simple_hash(self):
|
||||||
""" Tests the simple_hash function """
|
""" Tests the simple_hash function """
|
||||||
|
|
||||||
# no key, no salt, md5
|
# no key, no salt, md5
|
||||||
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg='md5')
|
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg='md5')
|
||||||
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
|
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
|
||||||
|
|
||||||
# no key, no salt, sha1
|
# no key, no salt, sha1
|
||||||
data_sha1 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha1')
|
data_sha1 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha1')
|
||||||
self.assertEqual(data_sha1, '00489a46753d8db260c71542611cdef80652c4b7')
|
self.assertEqual(data_sha1, '00489a46753d8db260c71542611cdef80652c4b7')
|
||||||
|
|
||||||
# no key, no salt, sha224
|
# no key, no salt, sha224
|
||||||
data_sha224 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha224')
|
data_sha224 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha224')
|
||||||
self.assertEqual(data_sha224, '84d7054271842c2c17983baa2b1447e0289d101140a8c002d49d60da')
|
self.assertEqual(data_sha224, '84d7054271842c2c17983baa2b1447e0289d101140a8c002d49d60da')
|
||||||
|
|
||||||
# no key, no salt, sha256
|
# no key, no salt, sha256
|
||||||
data_sha256 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha256')
|
data_sha256 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha256')
|
||||||
self.assertEqual(data_sha256, '0849f224d8deb267e4598702aaec1bd749e6caec90832469891012a4be24af08')
|
self.assertEqual(data_sha256, '0849f224d8deb267e4598702aaec1bd749e6caec90832469891012a4be24af08')
|
||||||
|
|
||||||
# no key, no salt, sha384
|
# no key, no salt, sha384
|
||||||
data_sha384 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha384')
|
data_sha384 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha384')
|
||||||
self.assertEqual(data_sha384,
|
self.assertEqual(data_sha384,
|
||||||
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
|
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
|
||||||
|
|
||||||
# no key, no salt, sha512
|
# no key, no salt, sha512
|
||||||
data_sha512 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha512')
|
data_sha512 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha512')
|
||||||
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
|
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
|
||||||
|
|||||||
+736
-270
File diff suppressed because it is too large
Load Diff
+88
-47
@@ -22,7 +22,7 @@ import decimal
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
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 FieldVirtual, FieldMethod
|
from pydal.objects import Field, FieldVirtual, FieldMethod
|
||||||
|
|
||||||
regex_isint = re.compile('^[+-]?\d+$')
|
regex_isint = re.compile('^[+-]?\d+$')
|
||||||
|
|
||||||
@@ -509,34 +509,44 @@ class IS_IN_DB(Validator):
|
|||||||
zero='',
|
zero='',
|
||||||
sort=False,
|
sort=False,
|
||||||
_and=None,
|
_and=None,
|
||||||
left=None
|
left=None,
|
||||||
|
delimiter=None,
|
||||||
|
auto_add=False,
|
||||||
):
|
):
|
||||||
from pydal.objects import Table
|
from pydal.objects import Table
|
||||||
if isinstance(field, Table):
|
|
||||||
field = field._id
|
|
||||||
|
|
||||||
if hasattr(dbset, 'define_table'):
|
if hasattr(dbset, 'define_table'):
|
||||||
self.dbset = dbset()
|
self.dbset = dbset()
|
||||||
else:
|
else:
|
||||||
self.dbset = dbset
|
self.dbset = dbset
|
||||||
|
|
||||||
|
if isinstance(field, Table):
|
||||||
|
field = field._id
|
||||||
|
elif isinstance(field, str):
|
||||||
|
items = field.split('.')
|
||||||
|
if len(items)==1: items+=['id']
|
||||||
|
field = self.dbset.db[items[0]][items[1]]
|
||||||
|
|
||||||
(ktable, kfield) = str(field).split('.')
|
(ktable, kfield) = str(field).split('.')
|
||||||
if not label:
|
if not label:
|
||||||
label = '%%(%s)s' % kfield
|
label = '%%(%s)s' % kfield
|
||||||
if isinstance(label, str):
|
if isinstance(label, str):
|
||||||
if regex1.match(str(label)):
|
if regex1.match(str(label)):
|
||||||
label = '%%(%s)s' % str(label).split('.')[-1]
|
label = '%%(%s)s' % str(label).split('.')[-1]
|
||||||
ks = regex2.findall(label)
|
fieldnames = regex2.findall(label)
|
||||||
if kfield not in ks:
|
if kfield not in fieldnames:
|
||||||
ks += [kfield]
|
fieldnames.append(kfield) # kfield must be last
|
||||||
fields = ks
|
elif isinstance(label, Field):
|
||||||
|
fieldnames = [label.name, kfield] # kfield must be last
|
||||||
|
label = '%%(%s)s' % label.name
|
||||||
|
elif callable(label):
|
||||||
|
fieldnames = '*'
|
||||||
else:
|
else:
|
||||||
ks = [kfield]
|
raise NotImplementedError
|
||||||
fields = 'all'
|
self.field = field # the lookup field
|
||||||
self.fields = fields
|
self.fieldnames = fieldnames # fields requires to build the formatting
|
||||||
self.label = label
|
self.label = label
|
||||||
self.ktable = ktable
|
self.ktable = ktable
|
||||||
self.kfield = kfield
|
self.kfield = kfield
|
||||||
self.ks = ks
|
|
||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
self.theset = None
|
self.theset = None
|
||||||
self.orderby = orderby
|
self.orderby = orderby
|
||||||
@@ -548,6 +558,8 @@ class IS_IN_DB(Validator):
|
|||||||
self.sort = sort
|
self.sort = sort
|
||||||
self._and = _and
|
self._and = _and
|
||||||
self.left = left
|
self.left = left
|
||||||
|
self.delimiter = delimiter
|
||||||
|
self.auto_add = auto_add
|
||||||
|
|
||||||
def set_self_id(self, id):
|
def set_self_id(self, id):
|
||||||
if self._and:
|
if self._and:
|
||||||
@@ -555,10 +567,10 @@ class IS_IN_DB(Validator):
|
|||||||
|
|
||||||
def build_set(self):
|
def build_set(self):
|
||||||
table = self.dbset.db[self.ktable]
|
table = self.dbset.db[self.ktable]
|
||||||
if self.fields == 'all':
|
if self.fieldnames == '*':
|
||||||
fields = [f for f in table]
|
fields = [f for f in table]
|
||||||
else:
|
else:
|
||||||
fields = [table[k] for k in self.fields]
|
fields = [table[k] for k in self.fieldnames]
|
||||||
ignore = (FieldVirtual, FieldMethod)
|
ignore = (FieldVirtual, FieldMethod)
|
||||||
fields = filter(lambda f: not isinstance(f, ignore), fields)
|
fields = filter(lambda f: not isinstance(f, ignore), fields)
|
||||||
if self.dbset.db._dbname != 'gae':
|
if self.dbset.db._dbname != 'gae':
|
||||||
@@ -591,18 +603,42 @@ class IS_IN_DB(Validator):
|
|||||||
items.insert(0, ('', self.zero))
|
items.insert(0, ('', self.zero))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def maybe_add(self, table, fieldname, value):
|
||||||
|
d = {fieldname: value}
|
||||||
|
record = table(**d)
|
||||||
|
if record:
|
||||||
|
return record.id
|
||||||
|
else:
|
||||||
|
return table.insert(**d)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
table = self.dbset.db[self.ktable]
|
table = self.dbset.db[self.ktable]
|
||||||
field = table[self.kfield]
|
field = table[self.kfield]
|
||||||
|
|
||||||
if self.multiple:
|
if self.multiple:
|
||||||
if self._and:
|
if self._and:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
values = value
|
values = value
|
||||||
|
elif self.delimiter:
|
||||||
|
values = value.split(self.delimiter) # because of autocomplete
|
||||||
elif value:
|
elif value:
|
||||||
values = [value]
|
values = [value]
|
||||||
else:
|
else:
|
||||||
values = []
|
values = []
|
||||||
|
|
||||||
|
if self.field.type in ('id','integer'):
|
||||||
|
new_values = []
|
||||||
|
for value in values:
|
||||||
|
if isinstance(value,(int,long)) or value.isdigit():
|
||||||
|
value = int(value)
|
||||||
|
elif self.auto_add:
|
||||||
|
value = self.maybe_add(table, self.fieldnames[0], value)
|
||||||
|
else:
|
||||||
|
return (values, translate(self.error_message))
|
||||||
|
new_values.append(value)
|
||||||
|
values = new_values
|
||||||
|
|
||||||
if isinstance(self.multiple, (tuple, list)) and \
|
if isinstance(self.multiple, (tuple, list)) and \
|
||||||
not self.multiple[0] <= len(values) < self.multiple[1]:
|
not self.multiple[0] <= len(values) < self.multiple[1]:
|
||||||
return (values, translate(self.error_message))
|
return (values, translate(self.error_message))
|
||||||
@@ -621,18 +657,32 @@ class IS_IN_DB(Validator):
|
|||||||
return (values, None)
|
return (values, None)
|
||||||
elif count(values) == len(values):
|
elif count(values) == len(values):
|
||||||
return (values, None)
|
return (values, None)
|
||||||
elif self.theset:
|
|
||||||
if str(value) in self.theset:
|
|
||||||
if self._and:
|
|
||||||
return self._and(value)
|
|
||||||
else:
|
|
||||||
return (value, None)
|
|
||||||
else:
|
else:
|
||||||
if self.dbset(field == value).count():
|
if self.field.type in ('id','integer'):
|
||||||
if self._and:
|
if isinstance(value,(int,long)) or value.isdigit():
|
||||||
return self._and(value)
|
value = int(value)
|
||||||
|
elif self.auto_add:
|
||||||
|
value = self.maybe_add(table, self.fieldnames[0], value)
|
||||||
else:
|
else:
|
||||||
return (value, None)
|
return (value, translate(self.error_message))
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = int(value)
|
||||||
|
except TypeError:
|
||||||
|
return (values, translate(self.error_message))
|
||||||
|
|
||||||
|
if self.theset:
|
||||||
|
if str(value) in self.theset:
|
||||||
|
if self._and:
|
||||||
|
return self._and(value)
|
||||||
|
else:
|
||||||
|
return (value, None)
|
||||||
|
else:
|
||||||
|
if self.dbset(field == value).count():
|
||||||
|
if self._and:
|
||||||
|
return self._and(value)
|
||||||
|
else:
|
||||||
|
return (value, None)
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
|
|
||||||
|
|
||||||
@@ -694,7 +744,7 @@ class IS_NOT_IN_DB(Validator):
|
|||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
else:
|
else:
|
||||||
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
|
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
|
||||||
if row and str(row.id) != str(id):
|
if row and str(row[table._id]) != str(id):
|
||||||
return (value, translate(self.error_message))
|
return (value, translate(self.error_message))
|
||||||
return (value, None)
|
return (value, None)
|
||||||
|
|
||||||
@@ -2165,29 +2215,22 @@ class IS_DATE(Validator):
|
|||||||
INPUT(_type='text', _name='name', requires=IS_DATE())
|
INPUT(_type='text', _name='name', requires=IS_DATE())
|
||||||
|
|
||||||
date has to be in the ISO8960 format YYYY-MM-DD
|
date has to be in the ISO8960 format YYYY-MM-DD
|
||||||
timezome must be None or a pytz.timezone("America/Chicago") object
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, format='%Y-%m-%d',
|
def __init__(self, format='%Y-%m-%d',
|
||||||
error_message='Enter date as %(format)s',
|
error_message='Enter date as %(format)s'):
|
||||||
timezone=None):
|
|
||||||
self.format = translate(format)
|
self.format = translate(format)
|
||||||
self.error_message = str(error_message)
|
self.error_message = str(error_message)
|
||||||
self.timezone = timezone
|
|
||||||
self.extremes = {}
|
self.extremes = {}
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
ovalue = value
|
ovalue = value
|
||||||
if isinstance(value, datetime.date):
|
if isinstance(value, datetime.date):
|
||||||
if self.timezone is not None:
|
|
||||||
value = value - datetime.timedelta(seconds=self.timezone*3600)
|
|
||||||
return (value, None)
|
return (value, None)
|
||||||
try:
|
try:
|
||||||
(y, m, d, hh, mm, ss, t0, t1, t2) = \
|
(y, m, d, hh, mm, ss, t0, t1, t2) = \
|
||||||
time.strptime(value, str(self.format))
|
time.strptime(value, str(self.format))
|
||||||
value = datetime.date(y, m, d)
|
value = datetime.date(y, m, d)
|
||||||
if self.timezone is not None:
|
|
||||||
value = self.timezone.localize(value).astimezone(utc)
|
|
||||||
return (value, None)
|
return (value, None)
|
||||||
except:
|
except:
|
||||||
self.extremes.update(IS_DATETIME.nice(self.format))
|
self.extremes.update(IS_DATETIME.nice(self.format))
|
||||||
@@ -2203,11 +2246,7 @@ class IS_DATE(Validator):
|
|||||||
format = format.replace('%Y', y)
|
format = format.replace('%Y', y)
|
||||||
if year < 1900:
|
if year < 1900:
|
||||||
year = 2000
|
year = 2000
|
||||||
if self.timezone is not None:
|
d = datetime.date(year, value.month, value.day)
|
||||||
d = datetime.datetime(year, value.month, value.day)
|
|
||||||
d = d.replace(tzinfo=utc).astimezone(self.timezone)
|
|
||||||
else:
|
|
||||||
d = datetime.date(year, value.month, value.day)
|
|
||||||
return d.strftime(format)
|
return d.strftime(format)
|
||||||
|
|
||||||
|
|
||||||
@@ -2258,7 +2297,8 @@ class IS_DATETIME(Validator):
|
|||||||
time.strptime(value, str(self.format))
|
time.strptime(value, str(self.format))
|
||||||
value = datetime.datetime(y, m, d, hh, mm, ss)
|
value = datetime.datetime(y, m, d, hh, mm, ss)
|
||||||
if self.timezone is not None:
|
if self.timezone is not None:
|
||||||
value = self.timezone.localize(value).astimezone(utc)
|
# TODO: https://github.com/web2py/web2py/issues/1094 (temporary solution)
|
||||||
|
value = self.timezone.localize(value).astimezone(utc).replace(tzinfo=None)
|
||||||
return (value, None)
|
return (value, None)
|
||||||
except:
|
except:
|
||||||
self.extremes.update(IS_DATETIME.nice(self.format))
|
self.extremes.update(IS_DATETIME.nice(self.format))
|
||||||
@@ -2307,8 +2347,7 @@ class IS_DATE_IN_RANGE(IS_DATE):
|
|||||||
minimum=None,
|
minimum=None,
|
||||||
maximum=None,
|
maximum=None,
|
||||||
format='%Y-%m-%d',
|
format='%Y-%m-%d',
|
||||||
error_message=None,
|
error_message=None):
|
||||||
timezone=None):
|
|
||||||
self.minimum = minimum
|
self.minimum = minimum
|
||||||
self.maximum = maximum
|
self.maximum = maximum
|
||||||
if error_message is None:
|
if error_message is None:
|
||||||
@@ -2320,8 +2359,7 @@ class IS_DATE_IN_RANGE(IS_DATE):
|
|||||||
error_message = "Enter date in range %(min)s %(max)s"
|
error_message = "Enter date in range %(min)s %(max)s"
|
||||||
IS_DATE.__init__(self,
|
IS_DATE.__init__(self,
|
||||||
format=format,
|
format=format,
|
||||||
error_message=error_message,
|
error_message=error_message)
|
||||||
timezone=timezone)
|
|
||||||
self.extremes = dict(min=self.formatter(minimum),
|
self.extremes = dict(min=self.formatter(minimum),
|
||||||
max=self.formatter(maximum))
|
max=self.formatter(maximum))
|
||||||
|
|
||||||
@@ -2847,9 +2885,11 @@ class CRYPT(object):
|
|||||||
self.salt = salt
|
self.salt = salt
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
value = value and value[:self.max_length]
|
v = value and str(value)[:self.max_length]
|
||||||
if len(value) < self.min_length:
|
if not v or len(v) < self.min_length:
|
||||||
return ('', translate(self.error_message))
|
return ('', translate(self.error_message))
|
||||||
|
if isinstance(value, LazyCrypt):
|
||||||
|
return (value, None)
|
||||||
return (LazyCrypt(self, value), None)
|
return (LazyCrypt(self, value), None)
|
||||||
|
|
||||||
# entropy calculator for IS_STRONG
|
# entropy calculator for IS_STRONG
|
||||||
@@ -3377,7 +3417,8 @@ class IS_IPV4(Validator):
|
|||||||
(number == self.localhost)):
|
(number == self.localhost)):
|
||||||
ok = False
|
ok = False
|
||||||
if not (self.is_private is None or self.is_private ==
|
if not (self.is_private is None or self.is_private ==
|
||||||
(sum([number[0] <= number <= number[1] for number in self.private]) > 0)):
|
(sum([private_number[0] <= number <= private_number[1]
|
||||||
|
for private_number in self.private]) > 0)):
|
||||||
ok = False
|
ok = False
|
||||||
if not (self.is_automatic is None or self.is_automatic ==
|
if not (self.is_automatic is None or self.is_automatic ==
|
||||||
(self.automatic[0] <= number <= self.automatic[1])):
|
(self.automatic[0] <= number <= self.automatic[1])):
|
||||||
|
|||||||
+7
-7
@@ -40,8 +40,8 @@ ProgramInfo = '''%s
|
|||||||
%s
|
%s
|
||||||
%s''' % (ProgramName, ProgramAuthor, ProgramVersion)
|
%s''' % (ProgramName, ProgramAuthor, ProgramVersion)
|
||||||
|
|
||||||
if not sys.version[:3] in ['2.5', '2.6', '2.7']:
|
if not sys.version[:3] in ['2.6', '2.7']:
|
||||||
msg = 'Warning: web2py requires Python 2.5, 2.6 or 2.7 but you are running:\n%s'
|
msg = 'Warning: web2py requires Python 2.6 or 2.7 but you are running:\n%s'
|
||||||
msg = msg % sys.version
|
msg = msg % sys.version
|
||||||
sys.stderr.write(msg)
|
sys.stderr.write(msg)
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ def run_system_tests(options):
|
|||||||
major_version = sys.version_info[0]
|
major_version = sys.version_info[0]
|
||||||
minor_version = sys.version_info[1]
|
minor_version = sys.version_info[1]
|
||||||
if major_version == 2:
|
if major_version == 2:
|
||||||
if minor_version in (5, 6):
|
if minor_version in (6,):
|
||||||
sys.stderr.write("Python 2.5 or 2.6\n")
|
sys.stderr.write('Python 2.6\n')
|
||||||
ret = subprocess.call(['unit2', '-v', 'gluon.tests'])
|
ret = subprocess.call(['unit2', '-v', 'gluon.tests'])
|
||||||
elif minor_version in (7,):
|
elif minor_version in (7,):
|
||||||
call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
|
call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
|
||||||
@@ -1117,12 +1117,12 @@ def start(cron=True):
|
|||||||
if hasattr(options, key):
|
if hasattr(options, key):
|
||||||
setattr(options, key, getattr(options2, key))
|
setattr(options, key, getattr(options2, key))
|
||||||
|
|
||||||
logfile0 = os.path.join('extras', 'examples', 'logging.example.conf')
|
logfile0 = os.path.join('examples', 'logging.example.conf')
|
||||||
if not os.path.exists('logging.conf') and os.path.exists(logfile0):
|
if not os.path.exists('logging.conf') and os.path.exists(logfile0):
|
||||||
import shutil
|
import shutil
|
||||||
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
|
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
|
||||||
shutil.copyfile('logging.example.conf', logfile0)
|
shutil.copyfile(logfile0, 'logging.conf')
|
||||||
sys.stdout.write("OK\n")
|
sys.stdout.write('OK\n')
|
||||||
|
|
||||||
# ## if -T run doctests (no cron)
|
# ## if -T run doctests (no cron)
|
||||||
if hasattr(options, 'test') and options.test:
|
if hasattr(options, 'test') and options.test:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
echo "This script will:
|
echo "This script will:
|
||||||
1) Install modules needed to run web2py on Fedora and CentOS/RHEL
|
1) Install modules needed to run web2py on Fedora and CentOS/RHEL
|
||||||
2) Install Python 2.6 to /opt and recompile wsgi if not provided
|
2) Install Python 2.6 to /opt and recompile wsgi if not provided
|
||||||
@@ -27,7 +28,7 @@ Press ENTER to continue...[ctrl+C to abort]"
|
|||||||
|
|
||||||
read CONFIRM
|
read CONFIRM
|
||||||
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
###
|
###
|
||||||
### Phase 0 - This may get messy. Lets work from a temporary directory
|
### Phase 0 - This may get messy. Lets work from a temporary directory
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
echo "This script will:
|
echo "This script will:
|
||||||
1) install all modules need to run web2py on Ubuntu 14.04
|
1) install all modules need to run web2py on Ubuntu 14.04
|
||||||
2) install web2py in /home/www-data/
|
2) install web2py in /home/www-data/
|
||||||
@@ -12,7 +13,7 @@ Press a key to continue...[ctrl+C to abort]"
|
|||||||
|
|
||||||
read CONFIRM
|
read CONFIRM
|
||||||
|
|
||||||
#!/bin/bash
|
|
||||||
# optional
|
# optional
|
||||||
# dpkg-reconfigure console-setup
|
# dpkg-reconfigure console-setup
|
||||||
# dpkg-reconfigure timezoneconf
|
# dpkg-reconfigure timezoneconf
|
||||||
@@ -88,9 +89,27 @@ WSGIDaemonProcess web2py user=www-data group=www-data
|
|||||||
|
|
||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
|
|
||||||
RewriteEngine On
|
WSGIProcessGroup web2py
|
||||||
RewriteCond %{HTTPS} !=on
|
WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py
|
||||||
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
|
WSGIPassAuthorization On
|
||||||
|
|
||||||
|
<Directory /home/www-data/web2py>
|
||||||
|
AllowOverride None
|
||||||
|
Require all denied
|
||||||
|
<Files wsgihandler.py>
|
||||||
|
Require all granted
|
||||||
|
</Files>
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) \
|
||||||
|
/home/www-data/web2py/applications/$1/static/$2
|
||||||
|
|
||||||
|
<Directory /home/www-data/web2py/applications/*/static/>
|
||||||
|
Options -Indexes
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresDefault "access plus 1 hour"
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
CustomLog /var/log/apache2/access.log common
|
CustomLog /var/log/apache2/access.log common
|
||||||
ErrorLog /var/log/apache2/error.log
|
ErrorLog /var/log/apache2/error.log
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from multiprocessing import freeze_support
|
||||||
|
# import gluon.import_all ##### This should be uncommented for py2exe.py
|
||||||
|
|
||||||
if hasattr(sys, 'frozen'):
|
if hasattr(sys, 'frozen'):
|
||||||
path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe
|
path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe
|
||||||
@@ -14,17 +16,14 @@ os.chdir(path)
|
|||||||
|
|
||||||
sys.path = [path] + [p for p in sys.path if not p == path]
|
sys.path = [path] + [p for p in sys.path if not p == path]
|
||||||
|
|
||||||
# import gluon.import_all ##### This should be uncommented for py2exe.py
|
# important that this import is after the os.chdir
|
||||||
|
|
||||||
import gluon.widget
|
import gluon.widget
|
||||||
|
|
||||||
# Start Web2py and Web2py cron service!
|
# Start Web2py and Web2py cron service!
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
freeze_support()
|
||||||
from multiprocessing import freeze_support
|
if 'COVERAGE_PROCESS_START' in os.environ:
|
||||||
freeze_support()
|
|
||||||
except:
|
|
||||||
sys.stderr.write('Sorry, -K only supported for python 2.6-2.7\n')
|
|
||||||
if os.environ.has_key("COVERAGE_PROCESS_START"):
|
|
||||||
try:
|
try:
|
||||||
import coverage
|
import coverage
|
||||||
coverage.process_startup()
|
coverage.process_startup()
|
||||||
|
|||||||
Reference in New Issue
Block a user