Merge branch 'master' into pydal-pip
Conflicts: gluon/__init__.py gluon/dal/_load.py gluon/dal/adapters/base.py gluon/dal/adapters/google.py gluon/dal/adapters/postgres.py gluon/dal/adapters/teradata.py gluon/dal/base.py gluon/dal/objects.py gluon/tests/__init__.py gluon/tests/test_dal_nosql.py
This commit is contained in:
@@ -1,3 +1,21 @@
|
|||||||
|
## 2.9.12
|
||||||
|
|
||||||
|
- Tornado HTTPS support, thanks Diego
|
||||||
|
- Modular DAL, thanks Giovanni
|
||||||
|
- Added coverage support, thanks Niphlod
|
||||||
|
- More tests, thanks Niphlod and Paolo Valleri
|
||||||
|
- Added support for show_if in readonly sqlform, thanks Paolo
|
||||||
|
- Improved scheduler, thanks Niphlod
|
||||||
|
- Email timeout support
|
||||||
|
- Made web2py's custom_import work with circular imports, thanks Jack Kuan
|
||||||
|
- Added Portuguese, Catalan, and Burmese translations
|
||||||
|
- Allow map_hyphen to work for application names, thanks Tim Nyborg
|
||||||
|
- New module appconfig.py, thanks Niphlod
|
||||||
|
- Added geospatial support to Teradata adaptor, thanks Andrew Willimott
|
||||||
|
- Many bug fixes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 2.9.6 - 2.9.10
|
## 2.9.6 - 2.9.10
|
||||||
|
|
||||||
- fixed support of GAE + SQL
|
- fixed support of GAE + SQL
|
||||||
|
|||||||
@@ -37,7 +37,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.10.0-beta+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
echo 'Version 2.9.12-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
|
||||||
@@ -61,7 +61,7 @@ src:
|
|||||||
### build web2py_src.zip
|
### build web2py_src.zip
|
||||||
echo '' > NEWINSTALL
|
echo '' > NEWINSTALL
|
||||||
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
||||||
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/dal/* web2py/gluon/contrib/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||||
|
|
||||||
mdp:
|
mdp:
|
||||||
make src
|
make src
|
||||||
|
|||||||
+3
-3
@@ -15,13 +15,13 @@ Then edit ./app.yaml and replace "yourappname" with yourappname.
|
|||||||
|
|
||||||
## Documentation (readthedocs.org)
|
## Documentation (readthedocs.org)
|
||||||
|
|
||||||
[](http://web2py.rtfd.org/)
|
[](http://web2py.rtfd.org/)
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
[](https://travis-ci.org/web2py/web2py)
|
[](https://travis-ci.org/web2py/web2py)
|
||||||
|
|
||||||
[](https://coveralls.io/r/web2py/web2py)
|
[](https://coveralls.io/r/web2py/web2py)
|
||||||
|
|
||||||
## Installation Instructions
|
## Installation Instructions
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.10.0-beta+timestamp.2014.10.16.15.58.50
|
Version 2.9.12-stable+timestamp.2015.01.17.00.07.04
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ def interact():
|
|||||||
f_globals = {}
|
f_globals = {}
|
||||||
for name, value in env['globals'].items():
|
for name, value in env['globals'].items():
|
||||||
if name not in gluon.html.__all__ and \
|
if name not in gluon.html.__all__ and \
|
||||||
name not in gluon.validators.__all__ and \
|
name not in gluon.validators.__all__:
|
||||||
name not in gluon.dal.__all__:
|
|
||||||
f_globals[name] = pydoc.text.repr(value)
|
f_globals[name] = pydoc.text.repr(value)
|
||||||
else:
|
else:
|
||||||
f_locals = {}
|
f_locals = {}
|
||||||
|
|||||||
@@ -1278,7 +1278,7 @@ def create_file():
|
|||||||
path = abspath(request.vars.location)
|
path = abspath(request.vars.location)
|
||||||
else:
|
else:
|
||||||
if request.vars.dir:
|
if request.vars.dir:
|
||||||
request.vars.location += request.vars.dir + '/'
|
request.vars.location += request.vars.dir + '/'
|
||||||
app = get_app(name=request.vars.location.split('/')[0])
|
app = get_app(name=request.vars.location.split('/')[0])
|
||||||
path = apath(request.vars.location, r=request)
|
path = apath(request.vars.location, r=request)
|
||||||
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
||||||
@@ -1434,36 +1434,36 @@ def create_file():
|
|||||||
""" % URL('edit', args=[app,request.vars.dir,filename])
|
""" % URL('edit', args=[app,request.vars.dir,filename])
|
||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
redirect(request.vars.sender + anchor)
|
redirect(request.vars.sender + anchor)
|
||||||
|
|
||||||
|
|
||||||
def listfiles(app, dir, regexp='.*\.py$'):
|
def listfiles(app, dir, regexp='.*\.py$'):
|
||||||
files = sorted(
|
files = sorted(
|
||||||
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
||||||
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def editfile(path,file,vars={}, app = None):
|
def editfile(path,file,vars={}, app = None):
|
||||||
args=(path,file) if 'app' in vars else (app,path,file)
|
args=(path,file) if 'app' in vars else (app,path,file)
|
||||||
url = URL('edit', args=args, vars=vars)
|
url = URL('edit', args=args, vars=vars)
|
||||||
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
|
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
|
||||||
|
|
||||||
def files_menu():
|
def files_menu():
|
||||||
app = request.vars.app or 'welcome'
|
app = request.vars.app or 'welcome'
|
||||||
dirs=[{'name':'models', 'reg':'.*\.py$'},
|
dirs=[{'name':'models', 'reg':'.*\.py$'},
|
||||||
{'name':'controllers', 'reg':'.*\.py$'},
|
{'name':'controllers', 'reg':'.*\.py$'},
|
||||||
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
||||||
{'name':'modules', 'reg':'.*\.py$'},
|
{'name':'modules', 'reg':'.*\.py$'},
|
||||||
{'name':'static', 'reg': '[^\.#].*'},
|
{'name':'static', 'reg': '[^\.#].*'},
|
||||||
{'name':'private', 'reg':'.*\.py$'}]
|
{'name':'private', 'reg':'.*\.py$'}]
|
||||||
result_files = []
|
result_files = []
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
|
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
|
||||||
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
|
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
|
||||||
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
|
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
|
||||||
_class="nav nav-list small-font"),
|
_class="nav nav-list small-font"),
|
||||||
_id=dir['name'] + '_files', _style="display: none;")))
|
_id=dir['name'] + '_files', _style="display: none;")))
|
||||||
return dict(result_files = result_files)
|
return dict(result_files = result_files)
|
||||||
|
|
||||||
def upload_file():
|
def upload_file():
|
||||||
""" File uploading handler """
|
""" File uploading handler """
|
||||||
@@ -1941,4 +1941,3 @@ def install_plugin():
|
|||||||
T('unable to install plugin "%s"', filename)
|
T('unable to install plugin "%s"', filename)
|
||||||
redirect(URL(f="plugins", args=[app,]))
|
redirect(URL(f="plugins", args=[app,]))
|
||||||
return dict(form=form, app=app, plugin=plugin, source=source)
|
return dict(form=form, app=app, plugin=plugin, source=source)
|
||||||
|
|
||||||
|
|||||||
@@ -325,7 +325,7 @@
|
|||||||
'unable to uninstall "%s"': 'impossibile disinstallare "%s"',
|
'unable to uninstall "%s"': 'impossibile disinstallare "%s"',
|
||||||
'unable to upgrade because "%s"': 'impossibile aggiornare perché "%s"',
|
'unable to upgrade because "%s"': 'impossibile aggiornare perché "%s"',
|
||||||
'uncheck all': 'smarca tutti',
|
'uncheck all': 'smarca tutti',
|
||||||
'Uninstall': 'disinstalla',
|
'Uninstall': 'Disinstalla',
|
||||||
'update': 'aggiorna',
|
'update': 'aggiorna',
|
||||||
'update all languages': 'aggiorna tutti i linguaggi',
|
'update all languages': 'aggiorna tutti i linguaggi',
|
||||||
'Update:': 'Aggiorna:',
|
'Update:': 'Aggiorna:',
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
'Versioning': 'Versioning',
|
'Versioning': 'Versioning',
|
||||||
'View': 'Vista',
|
'View': 'Vista',
|
||||||
'view': 'vista',
|
'view': 'vista',
|
||||||
'Views': 'viste',
|
'Views': 'Viste',
|
||||||
'views': 'viste',
|
'views': 'viste',
|
||||||
'Web Framework': 'Web Framework',
|
'Web Framework': 'Web Framework',
|
||||||
'web2py is up to date': 'web2py è aggiornato',
|
'web2py is up to date': 'web2py è aggiornato',
|
||||||
|
|||||||
@@ -47,5 +47,3 @@ if 'adminLanguage' in request.cookies and not (request.cookies['adminLanguage']
|
|||||||
#set static_version
|
#set static_version
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
response.static_version = global_settings.web2py_version.split('-')[0]
|
response.static_version = global_settings.web2py_version.split('-')[0]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,3 @@ else:
|
|||||||
URL(_a, 'default', f='logout')))
|
URL(_a, 'default', f='logout')))
|
||||||
response.menu.append((T('Debug'), False,
|
response.menu.append((T('Debug'), False,
|
||||||
URL(_a, 'debug', 'interact')))
|
URL(_a, 'debug', 'interact')))
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,3 @@ def stateWidget(field, value, data={'on-label':'Enabled', 'off-label':'Disabled'
|
|||||||
jQuery(".make-bootstrap-switch input[name='%s']").parent().bootstrapSwitch();
|
jQuery(".make-bootstrap-switch input[name='%s']").parent().bootstrapSwitch();
|
||||||
""" % fieldName)
|
""" % fieldName)
|
||||||
return DIV(div, script)
|
return DIV(div, script)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,492 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'!langcode!': 'ca',
|
||||||
|
'!langname!': 'Català',
|
||||||
|
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"actualizi" és una expressió opcional com "camp1=\'nou_valor\'". No es poden actualitzar o eliminar resultats de un JOIN',
|
||||||
|
'%(nrows)s records found': '%(nrows)s registres trobats',
|
||||||
|
'%s %%{position}': '%s %%{posició}',
|
||||||
|
'%s %%{row} deleted': '%s %%{fila} %%{eliminada}',
|
||||||
|
'%s %%{row} updated': '%s %%{fila} %%{actualitzada}',
|
||||||
|
'%s selected': '%s %%{seleccionat}',
|
||||||
|
'%Y-%m-%d': '%d/%m/%Y',
|
||||||
|
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||||
|
'(something like "it-it")': '(similar a "això-això")',
|
||||||
|
'@markmin\x01An error occured, please [[reload %s]] the page': 'Hi ha hagut un error, si us plau [[recarregui %s]] la pàgina',
|
||||||
|
'@markmin\x01Number of entries: **%s**': "Nombre d'entrades: **%s**",
|
||||||
|
'A new version of web2py is available': 'Hi ha una nova versió de wep2py disponible',
|
||||||
|
'A new version of web2py is available: %s': 'Hi ha una nova versió de wep2py disponible: %s',
|
||||||
|
'About': 'Sobre',
|
||||||
|
'about': 'sobre',
|
||||||
|
'About application': "Sobre l'aplicació",
|
||||||
|
'Access Control': "Control d'Accés",
|
||||||
|
'Add': 'Afegir',
|
||||||
|
'Add Record': 'Afegeix registre',
|
||||||
|
'additional code for your application': '`codi addicional per a la seva aplicació',
|
||||||
|
'admin disabled because no admin password': 'admin inhabilitat per falta de contrasenya',
|
||||||
|
'admin disabled because not supported on google app engine': 'admin inhabilitat, no és suportat en GAE',
|
||||||
|
'admin disabled because unable to access password file': 'admin inhabilitat, impossible accedir al fitxer con la contrasenya',
|
||||||
|
'Admin is disabled because insecure channel': 'Admin inhabilitat, el canal no és segur',
|
||||||
|
'Admin is disabled because unsecure channel': 'Admin inhabilitat, el canal no és segur',
|
||||||
|
'Administrative interface': 'Interfície administrativa',
|
||||||
|
'Administrative Interface': 'Interfície Administrativa',
|
||||||
|
'administrative interface': 'interfície administrativa',
|
||||||
|
'Administrator Password:': 'Contrasenya del Administrador:',
|
||||||
|
'Ajax Recipes': 'Receptes AJAX',
|
||||||
|
'An error occured, please %s the page': 'Hi ha hagut un error, per favor %s la pàgina',
|
||||||
|
'And': 'I',
|
||||||
|
'and rename it (required):': 'i renombri-la (requerit):',
|
||||||
|
'and rename it:': " i renombri'l:",
|
||||||
|
'appadmin': 'appadmin',
|
||||||
|
'appadmin is disabled because insecure channel': 'admin inhabilitat, el canal no és segur',
|
||||||
|
'application "%s" uninstalled': 'aplicació "%s" desinstal·lada',
|
||||||
|
'application compiled': 'aplicació compilada',
|
||||||
|
'application is compiled and cannot be designed': 'la aplicació està compilada i no pot ser modificada',
|
||||||
|
'Apply changes': 'Aplicar canvis',
|
||||||
|
'Appointment': 'Nomenament',
|
||||||
|
'Are you sure you want to delete file "%s"?': 'Està segur que vol eliminar el arxiu "%s"?',
|
||||||
|
'Are you sure you want to delete this object?': 'Està segur que vol esborrar aquest objecte?',
|
||||||
|
'Are you sure you want to uninstall application "%s"': '¿Està segur que vol desinstalar la aplicació "%s"',
|
||||||
|
'Are you sure you want to uninstall application "%s"?': '¿Està segur que vol desinstalar la aplicació "%s"?',
|
||||||
|
'at': 'a',
|
||||||
|
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENCIÓ: Inici de sessió requereix una connexió segura (HTTPS) o localhost.',
|
||||||
|
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENCION: NO EJECUTE VARIAS PRUEBAS SIMULTANEAMENTE, NO SON THREAD SAFE.',
|
||||||
|
'ATTENTION: you cannot edit the running application!': 'ATENCIO: no pot modificar la aplicació que està ejecutant-se!',
|
||||||
|
'Authentication': 'Autenticació',
|
||||||
|
'Authentication failed at client DB!': '¡La autenticació ha fallat en la BDD client!',
|
||||||
|
'Authentication failed at main DB!': '¡La autenticació ha fallat en la BDD principal!',
|
||||||
|
'Available Databases and Tables': 'Bases de dades i taules disponibles',
|
||||||
|
'Back': 'Endarrera',
|
||||||
|
'Buy this book': 'Compra aquest lllibre',
|
||||||
|
'Cache': 'Caché',
|
||||||
|
'cache': 'caché',
|
||||||
|
'Cache Cleared': 'Caché Netejada',
|
||||||
|
'Cache Keys': 'Claus de la Caché',
|
||||||
|
'cache, errors and sessions cleaned': 'caché, errors i sessions eliminats',
|
||||||
|
'Cannot be empty': 'No pot estar buit',
|
||||||
|
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se pot compilar: hi ha errors en la seva aplicació. Depuri, corregeixi errors i torni a intentar-ho.',
|
||||||
|
'cannot upload file "%(filename)s"': 'no és possible pujar fitxer "%(filename)s"',
|
||||||
|
'Change Password': 'Canviï la Contrasenya',
|
||||||
|
'Change password': 'Canviï la contrasenya',
|
||||||
|
'change password': 'canviï la contrasenya',
|
||||||
|
'Changelog': 'Changelog',
|
||||||
|
'check all': 'marcar tots',
|
||||||
|
'Check to delete': 'Marqui per a eliminar',
|
||||||
|
'choose one': 'escolliu un',
|
||||||
|
'clean': 'neteja',
|
||||||
|
'Clear': 'Netejar',
|
||||||
|
'Clear CACHE?': 'Netejar Memòrica Cau?',
|
||||||
|
'Clear DISK': 'Netejar DISC',
|
||||||
|
'Clear RAM': 'Netejar RAM',
|
||||||
|
'Click on the link %(link)s to reset your password': "Cliqui en l'enllaç %(link)s per a reiniciar la seva contrasenya",
|
||||||
|
'click to check for upgrades': 'feu clic per buscar actualitzacions',
|
||||||
|
'client': 'cliente',
|
||||||
|
'Client IP': 'IP del Client',
|
||||||
|
'Close': 'Tancar',
|
||||||
|
'Comma-separated export including columns not shown; fields from other tables are exported as raw values for faster export': 'Comma-separated export including columns not shown; fields from other tables are exported as raw values for faster export',
|
||||||
|
'Comma-separated export of visible columns. Fields from other tables are exported as they appear on-screen but this may be slow for many rows': 'Comma-separated export of visible columns. Fields from other tables are exported as they appear on-screen but this may be slow for many rows',
|
||||||
|
'Community': 'Comunitat',
|
||||||
|
'compile': 'compilar',
|
||||||
|
'compiled application removed': 'aplicació compilada eliminada',
|
||||||
|
'Components and Plugins': 'Components i Plugins',
|
||||||
|
'contains': 'conté',
|
||||||
|
'Controller': 'Controlador',
|
||||||
|
'Controllers': 'Controladors',
|
||||||
|
'controllers': 'controladors',
|
||||||
|
'Copyright': 'Copyright',
|
||||||
|
'Correo electrónico invàlid': 'Correu electrònic invàlid',
|
||||||
|
'create file with filename:': 'crear el fitxer amb el nom:',
|
||||||
|
'Create new application': 'Crear una nova aplicació',
|
||||||
|
'create new application:': 'crear una nova aplicació:',
|
||||||
|
'Create New Page': 'Crear Pàgina Nova',
|
||||||
|
'Create Page from Slug': 'Create Page from Slug',
|
||||||
|
'Created By': 'Creat Per',
|
||||||
|
'Created On': 'Creat a',
|
||||||
|
'CSV': 'CSV',
|
||||||
|
'CSV (hidden cols)': 'CSV (columnas ocultes)',
|
||||||
|
'Current request': 'Sol·licitud en curs',
|
||||||
|
'Current response': 'Resposta en curs',
|
||||||
|
'Current session': 'Sessió en curs',
|
||||||
|
'currently saved or': 'actualment guardat o',
|
||||||
|
'customize me!': "¡Adapta'm!",
|
||||||
|
'data uploaded': 'dades pujades',
|
||||||
|
'Database': 'Base de dades',
|
||||||
|
'Database %s select': 'selecció a base de dades %s',
|
||||||
|
'database administration': 'administració de base de dades',
|
||||||
|
'Database Administration (appadmin)': 'Administració de Base de Dades (appadmin)',
|
||||||
|
'Date and Time': 'Data i Hora',
|
||||||
|
'DB': 'BDD',
|
||||||
|
'db': 'bdd',
|
||||||
|
'DB Model': 'Model BDD',
|
||||||
|
'defines tables': 'defineix taules',
|
||||||
|
'Delete': 'Eliminar',
|
||||||
|
'delete': 'eliminar',
|
||||||
|
'delete all checked': 'eliminar marcats',
|
||||||
|
'Delete:': 'Eliminar:',
|
||||||
|
'Demo': 'Demostració',
|
||||||
|
'Deploy on Google App Engine': 'Desplegament a Google App Engine',
|
||||||
|
'Deployment Recipes': 'Receptes de desplegament',
|
||||||
|
'Description': 'Descripció',
|
||||||
|
'design': 'diseny',
|
||||||
|
'DESIGN': 'DISENY',
|
||||||
|
'Design for': 'Diseny per a',
|
||||||
|
'detecting': 'detectant',
|
||||||
|
'DISK': 'DISC',
|
||||||
|
'Disk Cache Keys': 'Claus de Caché en Disc',
|
||||||
|
'Disk Cleared': 'Disc netejat',
|
||||||
|
'Documentation': 'Documentació',
|
||||||
|
"Don't know what to do?": 'No sap què fer?',
|
||||||
|
'done!': '¡fet!',
|
||||||
|
'Download': 'Descàrregues',
|
||||||
|
'E-mail': 'Correu electrònic',
|
||||||
|
'edit': 'editar',
|
||||||
|
'EDIT': 'EDITAR',
|
||||||
|
'Edit': 'Editar',
|
||||||
|
'Edit application': 'Editar aplicació',
|
||||||
|
'edit controller': 'editar controlador',
|
||||||
|
'Edit current record': 'Editar el registre actual',
|
||||||
|
'Edit Menu': 'Editar Menu',
|
||||||
|
'Edit Page': 'Editar Pàgina',
|
||||||
|
'Edit Page Media': 'Edit Page Media',
|
||||||
|
'Edit Profile': 'Editar Perfil',
|
||||||
|
'edit profile': 'editar perfil',
|
||||||
|
'Edit This App': 'Editi aquesta App',
|
||||||
|
'Editing file': 'Editant fitxer',
|
||||||
|
'Editing file "%s"': 'Editant fitxer "%s"',
|
||||||
|
'El fitxer ha de ser PDF': 'El fitxer ha de ser PDF',
|
||||||
|
'El fitxer ha de ser PDF o XML': 'El fitxer ha de ser PDF o XML',
|
||||||
|
'Email': 'Email',
|
||||||
|
'Email and SMS': 'Correu electrònic i SMS',
|
||||||
|
'Email sent': 'Correu electrònic enviat',
|
||||||
|
'End of impersonation': 'Fi de suplantació',
|
||||||
|
'enter a number between %(min)g and %(max)g': 'introdueixi un número entre %(min)g i %(max)g',
|
||||||
|
'Enter a valid email address': 'Entri una adreça email vàlida',
|
||||||
|
'enter a value': 'entri un valor',
|
||||||
|
'Enter a value': 'Entri un valor',
|
||||||
|
'Enter an integer between %(min)g and %(max)g': 'Entri un numero enter entre %(min)g i %(max)g',
|
||||||
|
'enter an integer between %(min)g and %(max)g': 'entri numero enter entre %(min)g i %(max)g',
|
||||||
|
'enter date and time as %(format)s': 'entri data i hora com %(format)s',
|
||||||
|
'Enter from %(min)g to %(max)g characters': 'Entri des de %(min)g a %(max)g caràcters',
|
||||||
|
'Enter valid filename': 'Entri nom de fitxer vàlid',
|
||||||
|
'Error logs for "%(app)s"': 'Bitàcora de errors a "%(app)s"',
|
||||||
|
'errors': 'errors',
|
||||||
|
'Errors': 'Errors',
|
||||||
|
'Errors in form, please check it out.': 'Hi ha errors en el formulari, per favor comprovi-ho.',
|
||||||
|
'export as csv file': 'exportar com fitxer CSV',
|
||||||
|
'Export:': 'Exportar:',
|
||||||
|
'exposes': 'exposa',
|
||||||
|
'extends': 'extén',
|
||||||
|
'failed to reload module': 'la recàrrega del mòdul ha fallat',
|
||||||
|
'FAQ': 'FAQ',
|
||||||
|
'file': 'fitxer',
|
||||||
|
'file "%(filename)s" created': 'fitxer "%(filename)s" creat',
|
||||||
|
'file "%(filename)s" deleted': 'fitxer "%(filename)s" eliminat',
|
||||||
|
'file "%(filename)s" uploaded': 'fitxer "%(filename)s" pujat',
|
||||||
|
'file "%(filename)s" was not deleted': 'fitxer "%(filename)s" no fou eliminat',
|
||||||
|
'file "%s" of %s restored': 'fitxer "%s" de %s restaurat',
|
||||||
|
'file ## download': 'file ',
|
||||||
|
'file changed on disk': 'fitxer modificat en el disco',
|
||||||
|
'file does not exist': 'fitxer no existeix',
|
||||||
|
'file saved on %(time)s': 'fitxer guardat a %(time)s',
|
||||||
|
'file saved on %s': 'fitxer guardat a %s',
|
||||||
|
'First name': 'Nom',
|
||||||
|
'Forgot username?': 'Ha oblidat el nom de usuari?',
|
||||||
|
'Forms and Validators': 'Formularis i validadors',
|
||||||
|
'Free Applications': 'Aplicacions Lliures',
|
||||||
|
'Functions with no doctests will result in [passed] tests.': 'Funcions sense doctests equivalen a pruebas [aceptades].',
|
||||||
|
'Group %(group_id)s created': 'Grupo %(group_id)s creat',
|
||||||
|
'Group ID': 'ID de Grup',
|
||||||
|
'Group uniquely assigned to user %(id)s': 'Grup assignat únicament al usuari %(id)s',
|
||||||
|
'Groups': 'Grups',
|
||||||
|
'Hello': 'Hola',
|
||||||
|
'Hello World': 'Hola Món',
|
||||||
|
'help': 'ajuda',
|
||||||
|
'Home': 'Inici',
|
||||||
|
'Hosted by': 'Hosted by',
|
||||||
|
'How did you get here?': 'Com has arribat aquí?',
|
||||||
|
'HTML': 'HTML',
|
||||||
|
'HTML export of visible columns': 'HTML export de columnes visibles',
|
||||||
|
'htmledit': 'htmledit',
|
||||||
|
'Impersonate': 'Suplantar',
|
||||||
|
'import': 'importar',
|
||||||
|
'Import/Export': 'Importar/Exportar',
|
||||||
|
'in': 'a',
|
||||||
|
'includes': 'inclou',
|
||||||
|
'Index': 'Índex',
|
||||||
|
'insert new': 'inserti nou',
|
||||||
|
'insert new %s': 'inserti nou %s',
|
||||||
|
'Installed applications': 'Aplicacions instalades',
|
||||||
|
'Insufficient privileges': 'Privilegis insuficients',
|
||||||
|
'internal error': 'error intern',
|
||||||
|
'Internal State': 'Estat Intern',
|
||||||
|
'Introduction': 'Introducció',
|
||||||
|
'Invalid action': 'Acció invàlida',
|
||||||
|
'Invalid email': 'Correo electrónico invàlid',
|
||||||
|
'invalid expression': 'expressió invàlida',
|
||||||
|
'Invalid login': 'Inici de sessió invàlida',
|
||||||
|
'invalid password': 'contrasenya invàlida',
|
||||||
|
'Invalid Query': 'Consulta invàlida',
|
||||||
|
'invalid request': 'sol·licitud invàlida',
|
||||||
|
'Invalid reset password': 'Reinici de contrasenya invàlid',
|
||||||
|
'invalid ticket': 'tiquet invàlid',
|
||||||
|
'Is Active': 'Està Actiu',
|
||||||
|
'Key': 'Clau',
|
||||||
|
'language file "%(filename)s" created/updated': 'fitxer de llenguatge "%(filename)s" creat/actualitzat',
|
||||||
|
'Language files (static strings) updated': 'Fitxers de llenguatge (cadenes estàtiques) actualitzats',
|
||||||
|
'languages': 'llenguatges',
|
||||||
|
'Languages': 'Llenguatges',
|
||||||
|
'languages updated': 'llenguatges actualitzats',
|
||||||
|
'Last name': 'Cognom',
|
||||||
|
'Last saved on:': 'Guardat a:',
|
||||||
|
'Layout': 'Diseny de pàgina',
|
||||||
|
'Layout Plugins': 'Plugins de disseny',
|
||||||
|
'Layouts': 'Dissenys de pàgines',
|
||||||
|
'License for': 'Llicència per a',
|
||||||
|
'Live Chat': 'Xat en viu',
|
||||||
|
'loading...': 'carregant...',
|
||||||
|
'Log In': 'Log In',
|
||||||
|
'Log Out': 'Log Out',
|
||||||
|
'Logged in': 'Sessió iniciada',
|
||||||
|
'Logged out': 'Sessió finalitzada',
|
||||||
|
'Login': 'Inici de sessió',
|
||||||
|
'login': 'inici de sessió',
|
||||||
|
'Login disabled by administrator': 'Inici de sessió inhabilitat pel administrador',
|
||||||
|
'Login to the Administrative Interface': 'Inici de sessió per a la Interfície Administrativa',
|
||||||
|
'logout': 'fi de sessió',
|
||||||
|
'Logout': 'Fi de sessió',
|
||||||
|
'Lost Password': 'Contrasenya perdida',
|
||||||
|
'Lost password?': 'Ha oblidat la contrasenya?',
|
||||||
|
'lost password?': '¿ha oblidat la contrasenya?',
|
||||||
|
'Main Menu': 'Menú principal',
|
||||||
|
'Manage %(action)s': 'Manage %(action)s',
|
||||||
|
'Manage Access Control': 'Manage Access Control',
|
||||||
|
'Manage Cache': 'Gestionar la Caché',
|
||||||
|
'Menu Model': 'Model "menu"',
|
||||||
|
'merge': 'combinar',
|
||||||
|
'Models': 'Models',
|
||||||
|
'models': 'models',
|
||||||
|
'Modified By': 'Modificat Per',
|
||||||
|
'Modified On': 'Modificat A',
|
||||||
|
'Modules': 'Mòduls',
|
||||||
|
'modules': 'mòduls',
|
||||||
|
'must be YYYY-MM-DD HH:MM:SS!': '¡debe ser DD/MM/YYYY HH:MM:SS!',
|
||||||
|
'must be YYYY-MM-DD!': '¡debe ser DD/MM/YYYY!',
|
||||||
|
'My Sites': 'Els Meus Llocs',
|
||||||
|
'Name': 'Nombre',
|
||||||
|
'New': 'Nuevo',
|
||||||
|
'New %(entity)s': 'Nou %(entity)s',
|
||||||
|
'new application "%s" created': 'nova aplicació "%s" creada',
|
||||||
|
'New password': 'Contrasenya nova',
|
||||||
|
'New Record': 'Registre nou',
|
||||||
|
'new record inserted': 'nou registre insertat',
|
||||||
|
'New Search': 'Cerca nova',
|
||||||
|
'next %s rows': 'següents %s files',
|
||||||
|
'next 100 rows': '100 files següents',
|
||||||
|
'NO': 'NO',
|
||||||
|
'No databases in this application': 'No hi ha bases de dades en esta aplicació',
|
||||||
|
'No records found': "No s'han trobat registres",
|
||||||
|
'Not authorized': 'No autoritzat',
|
||||||
|
'not in': 'no a',
|
||||||
|
'Object or table name': 'Nom del objecte o taula',
|
||||||
|
'Old password': 'Contrasenya anterior',
|
||||||
|
'Online examples': 'Ejemples en línia',
|
||||||
|
'Or': 'O',
|
||||||
|
'or import from csv file': 'o importar desde fitxer CSV',
|
||||||
|
'or provide application url:': 'o proveeix URL de la aplicació:',
|
||||||
|
'Origin': 'Origen',
|
||||||
|
'Original/Translation': 'Original/Traducció',
|
||||||
|
'Other Plugins': 'Altres Plugins',
|
||||||
|
'Other Recipes': 'Altres Receptes',
|
||||||
|
'Overview': 'Resum',
|
||||||
|
'pack all': 'empaquetar tot',
|
||||||
|
'pack compiled': 'empaquetar compilats',
|
||||||
|
'Password': 'Contrasenya',
|
||||||
|
'Password changed': 'Contrasenya cambiada',
|
||||||
|
"Password fields don't match": 'Els camps de contrasenya no coincideixen',
|
||||||
|
'Password reset': 'Reinici de contrasenya',
|
||||||
|
'Peeking at file': 'Visualitzant fitxer',
|
||||||
|
'Permission': 'Permís',
|
||||||
|
'Permissions': 'Permisos',
|
||||||
|
'Phone': 'Telèfon',
|
||||||
|
'please input your password again': 'si us plau, entri un altre cop la seva contrasenya',
|
||||||
|
'Plugins': 'Plugins',
|
||||||
|
'Powered by': 'Aquest lloc utilitza',
|
||||||
|
'Preface': 'Prefaci',
|
||||||
|
'Presentar Factures': 'Presentar Factures',
|
||||||
|
'Presentar factures': 'Presentar factures',
|
||||||
|
'previous %s rows': '%s files prèvies',
|
||||||
|
'previous 100 rows': '100 files anteriors',
|
||||||
|
'Profile': 'Perfil',
|
||||||
|
'Profile updated': 'Perfil actualitzat',
|
||||||
|
'pygraphviz library not found': 'pygraphviz library not found',
|
||||||
|
'Python': 'Python',
|
||||||
|
'Query Not Supported: %s': 'Consulta No Suportada: %s',
|
||||||
|
'Query:': 'Consulta:',
|
||||||
|
'Quick Examples': 'Exemple Ràpids',
|
||||||
|
'RAM': 'RAM',
|
||||||
|
'RAM Cache Keys': 'Claus de la Caché en RAM',
|
||||||
|
'Ram Cleared': 'Ram Netjeda',
|
||||||
|
'Recipes': 'Receptes',
|
||||||
|
'Record': 'Registre',
|
||||||
|
'Record %(id)s created': 'Registre %(id)s creat',
|
||||||
|
'Record Created': 'Registre Creat',
|
||||||
|
'record does not exist': 'el registre no existe',
|
||||||
|
'Record ID': 'ID de Registre',
|
||||||
|
'Record id': 'Id de registre',
|
||||||
|
'Ref APB': 'Ref APB',
|
||||||
|
'register': "registri's",
|
||||||
|
'Register': "Registri's",
|
||||||
|
'Registration identifier': 'Identificador de Registre',
|
||||||
|
'Registration key': 'Clau de registre',
|
||||||
|
'Registration successful': 'Registre amb èxit',
|
||||||
|
'reload': 'recarregar',
|
||||||
|
'Remember me (for 30 days)': "Recordi'm (durant 30 dies)",
|
||||||
|
'remove compiled': 'eliminar compilades',
|
||||||
|
'Request reset password': 'Sol·licitud de restabliment de contrasenya',
|
||||||
|
'Reset password': 'Reiniciar contrasenya',
|
||||||
|
'Reset Password key': 'Restaurar Clau de la Contrasenya',
|
||||||
|
'Resolve Conflict file': 'Resolgui el Conflicte de fitxer',
|
||||||
|
'restore': 'restaurar',
|
||||||
|
'Retrieve username': 'Recuperar nom de usuari',
|
||||||
|
'revert': 'revertir',
|
||||||
|
'Role': 'Rol',
|
||||||
|
'Roles': 'Rols',
|
||||||
|
'Rows in Table': 'Files a la taula',
|
||||||
|
'Rows selected': 'Files seleccionades',
|
||||||
|
'save': 'guardar',
|
||||||
|
'Save model as...': 'Save model as...',
|
||||||
|
'Saved file hash:': 'Hash del fitxer guardat:',
|
||||||
|
'Search': 'Buscar',
|
||||||
|
'Search Pages': 'Search Pages',
|
||||||
|
'Semantic': 'Semàntica',
|
||||||
|
'Services': 'Serveis',
|
||||||
|
'session expired': 'sessió expirada',
|
||||||
|
'shell': 'terminal',
|
||||||
|
'Sign Up': 'Sign Up',
|
||||||
|
'site': 'lloc',
|
||||||
|
'Size of cache:': 'Mida de la Caché:',
|
||||||
|
'Slug': 'Slug',
|
||||||
|
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
|
||||||
|
'Spreadsheet-optimised export of tab-separated content including hidden columns. May be slow': 'Spreadsheet-optimised export of tab-separated content including hidden columns. May be slow',
|
||||||
|
'Spreadsheet-optimised export of tab-separated content, visible columns only. May be slow.': 'Spreadsheet-optimised export of tab-separated content, visible columns only. May be slow.',
|
||||||
|
'start': 'inici',
|
||||||
|
'Start building a new search': 'Start building a new search',
|
||||||
|
'starts with': 'comença per',
|
||||||
|
'state': 'estat',
|
||||||
|
'static': 'estàtics',
|
||||||
|
'Static files': 'Fitxers estàtics',
|
||||||
|
'Statistics': 'Estadístiques',
|
||||||
|
'Stylesheet': "Fulla d'estil",
|
||||||
|
'Submit': 'Enviar',
|
||||||
|
'submit': 'enviar',
|
||||||
|
'Success!': 'Correcte!',
|
||||||
|
'Support': 'Suport',
|
||||||
|
'Sure you want to delete this object?': '¿Està segur que vol eliminar aquest objecte?',
|
||||||
|
'Table': 'taula',
|
||||||
|
'Table name': 'Nom de la taula',
|
||||||
|
'test': 'provar',
|
||||||
|
'Testing application': 'Provant aplicació',
|
||||||
|
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "consulta" és una condición com "db.tabla1.campo1==\'valor\'". Algo com "db.tabla1.campo1==db.tabla2.campo2" resulta en un JOIN SQL.',
|
||||||
|
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lògica de la aplicació, cada ruta URL es mapeja en una funció exposada en el controlador',
|
||||||
|
'The Core': 'El Nucli',
|
||||||
|
'the data representation, define database tables and sets': 'la representació de dades, defineix taules i conjunts de base de dades',
|
||||||
|
'The output of the file is a dictionary that was rendered by the view %s': 'El resultat de aquesta funció és un diccionari que és desplegat per la vista %s',
|
||||||
|
'the presentations layer, views are also known as templates': 'la capa de presentació, les vistes també són anomenades plantilles',
|
||||||
|
'The Views': 'Les Vistes',
|
||||||
|
'There are no controllers': 'No hi ha controladors',
|
||||||
|
'There are no models': 'No hi ha models',
|
||||||
|
'There are no modules': 'No hi ha mòduls',
|
||||||
|
'There are no static files': 'No hi ha fitxers estàtics',
|
||||||
|
'There are no translators, only default language is supported': 'No hi ha traductors, només el llenguatge per defecte és suportat',
|
||||||
|
'There are no views': 'No hi ha vistes',
|
||||||
|
'these files are served without processing, your images go here': 'aquests fitxers són servits sense processar, les seves imatges van aquí',
|
||||||
|
'This App': 'Aquesta Aplicació',
|
||||||
|
'This email already has an account': 'Aquest correu electrònic ja té un compte',
|
||||||
|
'This is a copy of the scaffolding application': 'Aquesta és una còpia de la aplicació de bastiment',
|
||||||
|
'This is the %(filename)s template': 'Aquesta és la plantilla %(filename)s',
|
||||||
|
'Ticket': 'Tiquet',
|
||||||
|
'Time in Cache (h:m:s)': 'Temps en Caché (h:m:s)',
|
||||||
|
'Timestamp': 'Marca de temps',
|
||||||
|
'Title': 'Títol',
|
||||||
|
'to previous version.': 'a la versió prèvia.',
|
||||||
|
'To emulate a breakpoint programatically, write:': 'Emular un punto de ruptura programàticament, escribir:',
|
||||||
|
'to use the debugger!': 'usar el depurador!',
|
||||||
|
'toggle breakpoint': 'alternar punt de ruptura',
|
||||||
|
'Toggle comment': 'Alternar comentari',
|
||||||
|
'Toggle Fullscreen': 'Alternar pantalla completa',
|
||||||
|
'too short': 'massa curt',
|
||||||
|
'Traceback': 'Traceback',
|
||||||
|
'translation strings for the application': 'cadenes de caracters de traducció per a la aplicació',
|
||||||
|
'try': 'intenti',
|
||||||
|
'try something like': 'intenti algo com',
|
||||||
|
'TSV (Excel compatible)': 'TSV (compatible Excel)',
|
||||||
|
'TSV (Excel compatible, hidden cols)': 'TSV (compatible Excel, columnes ocultes)',
|
||||||
|
'TSV (Spreadsheets)': 'TSV (Fulls de càlcul)',
|
||||||
|
'TSV (Spreadsheets, hidden cols)': 'TSV (Fulls de càlcul, columnes amagades)',
|
||||||
|
'Twitter': 'Twitter',
|
||||||
|
'Unable to check for upgrades': 'No és possible verificar la existencia de actualitzacions',
|
||||||
|
'unable to create application "%s"': 'no és possible crear la aplicació "%s"',
|
||||||
|
'unable to delete file "%(filename)s"': 'no és possible eliminar el fitxer "%(filename)s"',
|
||||||
|
'Unable to download': 'No és possible la descàrrega',
|
||||||
|
'Unable to download app': 'No és possible descarregar la aplicació',
|
||||||
|
'unable to parse csv file': 'no és possible analitzar el fitxer CSV',
|
||||||
|
'unable to uninstall "%s"': 'no és possible instalar "%s"',
|
||||||
|
'uncheck all': 'desmarcar tots',
|
||||||
|
'uninstall': 'desinstalar',
|
||||||
|
'unknown': 'desconocido',
|
||||||
|
'update': 'actualitzar',
|
||||||
|
'update all languages': 'actualitzar tots els llenguatges',
|
||||||
|
'Update:': 'Actualizi:',
|
||||||
|
'upload application:': 'pujar aplicació:',
|
||||||
|
'Upload existing application': 'Puji aquesta aplicació',
|
||||||
|
'upload file:': 'puji fitxer:',
|
||||||
|
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, i ~(...) para NOT, para crear consultas més complexes.',
|
||||||
|
'User': 'Usuari',
|
||||||
|
'User %(id)s is impersonating %(other_id)s': 'El usuari %(id)s està suplantant %(other_id)s',
|
||||||
|
'User %(id)s Logged-in': 'El usuari %(id)s inicià la sessió',
|
||||||
|
'User %(id)s Logged-out': 'El usuari %(id)s finalitzà la sessió',
|
||||||
|
'User %(id)s Password changed': 'Contrasenya del usuari %(id)s canviada',
|
||||||
|
'User %(id)s Password reset': 'Contrasenya del usuari %(id)s reiniciada',
|
||||||
|
'User %(id)s Profile updated': 'Actualitzat el perfil del usuari %(id)s',
|
||||||
|
'User %(id)s Registered': 'Usuari %(id)s Registrat',
|
||||||
|
'User %(id)s Username retrieved': 'Se ha recuperat el nom de usuari del usuari %(id)s',
|
||||||
|
'User %(username)s Logged-in': 'El usuari %(username)s inicià la sessió',
|
||||||
|
"User '%(username)s' Logged-in": "El usuari '%(username)s' inicià la sessió",
|
||||||
|
"User '%(username)s' Logged-out": "El usuari '%(username)s' finalitzà la sessió",
|
||||||
|
'User Id': 'Id de Usuari',
|
||||||
|
'User ID': 'ID de Usuari',
|
||||||
|
'User Logged-out': 'El usuari finalitzà la sessió',
|
||||||
|
'Username': 'Nom de usuari',
|
||||||
|
'Username retrieve': 'Recuperar nom de usuari',
|
||||||
|
'Users': 'Usuaris',
|
||||||
|
'Value already in database or empty': 'El valor ya existeix en la base de dades o està buit',
|
||||||
|
'value already in database or empty': 'el valor ya existeix en la base de dades o està buit',
|
||||||
|
'value not allowed': 'valor no permès',
|
||||||
|
'Value not in database': 'El valor no està a la base de dades',
|
||||||
|
'value not in database': 'el valor no està a la base de dades',
|
||||||
|
'Verify Password': 'Verificar Contrasenya',
|
||||||
|
'Version': 'Versió',
|
||||||
|
'versioning': 'versions',
|
||||||
|
'Videos': 'Videos',
|
||||||
|
'View': 'Vista',
|
||||||
|
'view': 'vista',
|
||||||
|
'View %(entity)s': 'Veure %(entity)s',
|
||||||
|
'View Page': 'View Page',
|
||||||
|
'Views': 'Vistes',
|
||||||
|
'views': 'vistes',
|
||||||
|
'web2py is up to date': 'web2py està actualitzat',
|
||||||
|
'web2py Recent Tweets': 'Tweets Recents de web2py',
|
||||||
|
'Welcome': 'Benvingut',
|
||||||
|
'Welcome %s': 'Benvingut %s',
|
||||||
|
'Welcome to web2py': 'Benvingut a web2py',
|
||||||
|
'Welcome to web2py!': '¡Benvingut a web2py!',
|
||||||
|
'Which called the function %s located in the file %s': 'La qual va cridar la funció %s localitzada en el fitxer %s',
|
||||||
|
'Wiki Page': 'Wiki Page',
|
||||||
|
'Working...': 'Treballant ...',
|
||||||
|
'XML': 'XML',
|
||||||
|
'XML export of columns shown': 'XML export of columns shown',
|
||||||
|
'YES': 'SÍ',
|
||||||
|
'You are successfully running web2py': 'Vostè està executant web2py amb èxit',
|
||||||
|
'You can modify this application and adapt it to your needs': 'Vostè pot modificar aquesta aplicació i adaptar-la a les seves necessitats',
|
||||||
|
'You visited the url %s': 'Vostè va visitar la url %s',
|
||||||
|
'Your username is: %(username)s': 'El seu nom de usuari és: %(username)s',
|
||||||
|
}
|
||||||
+40
-7
@@ -128,10 +128,10 @@ class CacheAbstract(object):
|
|||||||
Auxiliary function called by `clear` to search and clear cache entries
|
Auxiliary function called by `clear` to search and clear cache entries
|
||||||
"""
|
"""
|
||||||
r = re.compile(regex)
|
r = re.compile(regex)
|
||||||
for key in storage:
|
for key in storage.keys():
|
||||||
if r.match(str(key)):
|
if r.match(str(key)):
|
||||||
del storage[key]
|
del storage[key]
|
||||||
break
|
return
|
||||||
|
|
||||||
|
|
||||||
class CacheInRam(CacheAbstract):
|
class CacheInRam(CacheAbstract):
|
||||||
@@ -246,11 +246,11 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
"""
|
"""
|
||||||
Disk based cache
|
Disk based cache
|
||||||
|
|
||||||
This is implemented as a shelve object and it is shared by multiple web2py
|
This is implemented as a key value store where each key corresponds to a
|
||||||
processes (and threads) as long as they share the same filesystem.
|
single file in disk which is replaced when the value changes.
|
||||||
|
|
||||||
Disk cache provides persistance when web2py is started/stopped but it slower
|
Disk cache provides persistance when web2py is started/stopped but it is
|
||||||
than `CacheInRam`
|
slower than `CacheInRam`
|
||||||
|
|
||||||
Values stored in disk cache must be pickable.
|
Values stored in disk cache must be pickable.
|
||||||
"""
|
"""
|
||||||
@@ -259,8 +259,11 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
"""
|
"""
|
||||||
Implements a key based storage in disk.
|
Implements a key based storage in disk.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, folder):
|
def __init__(self, folder):
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
|
self.key_filter_in = lambda key: key
|
||||||
|
self.key_filter_out = lambda key: key
|
||||||
# Check the best way to do atomic file replacement.
|
# Check the best way to do atomic file replacement.
|
||||||
if sys.version_info >= (3, 3):
|
if sys.version_info >= (3, 3):
|
||||||
self.replace = os.replace
|
self.replace = os.replace
|
||||||
@@ -287,6 +290,25 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
# POSIX rename() is always atomic
|
# POSIX rename() is always atomic
|
||||||
self.replace = os.rename
|
self.replace = os.rename
|
||||||
|
|
||||||
|
# Make sure we use valid filenames.
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import base64
|
||||||
|
def key_filter_in_windows(key):
|
||||||
|
"""
|
||||||
|
Windows doesn't allow \ / : * ? "< > | in filenames.
|
||||||
|
To go around this encode the keys with base32.
|
||||||
|
"""
|
||||||
|
return base64.b32encode(key)
|
||||||
|
|
||||||
|
def key_filter_out_windows(key):
|
||||||
|
"""
|
||||||
|
We need to decode the keys so regex based removal works.
|
||||||
|
"""
|
||||||
|
return base64.b32decode(key)
|
||||||
|
|
||||||
|
self.key_filter_in = key_filter_in_windows
|
||||||
|
self.key_filter_out = key_filter_out_windows
|
||||||
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
tmp_name, tmp_path = tempfile.mkstemp(dir=self.folder)
|
tmp_name, tmp_path = tempfile.mkstemp(dir=self.folder)
|
||||||
@@ -295,6 +317,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
pickle.dump((time.time(), value), tmp, pickle.HIGHEST_PROTOCOL)
|
pickle.dump((time.time(), value), tmp, pickle.HIGHEST_PROTOCOL)
|
||||||
finally:
|
finally:
|
||||||
tmp.close()
|
tmp.close()
|
||||||
|
key = self.key_filter_in(key)
|
||||||
fullfilename = os.path.join(self.folder, recfile.generate(key))
|
fullfilename = os.path.join(self.folder, recfile.generate(key))
|
||||||
if not os.path.exists(os.path.dirname(fullfilename)):
|
if not os.path.exists(os.path.dirname(fullfilename)):
|
||||||
os.makedirs(os.path.dirname(fullfilename))
|
os.makedirs(os.path.dirname(fullfilename))
|
||||||
@@ -302,24 +325,32 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
key = self.key_filter_in(key)
|
||||||
if recfile.exists(key, path=self.folder):
|
if recfile.exists(key, path=self.folder):
|
||||||
timestamp, value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
timestamp, value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
|
key = self.key_filter_in(key)
|
||||||
return recfile.exists(key, path=self.folder)
|
return recfile.exists(key, path=self.folder)
|
||||||
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
|
key = self.key_filter_in(key)
|
||||||
recfile.remove(key, path=self.folder)
|
recfile.remove(key, path=self.folder)
|
||||||
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for dirpath, dirnames, filenames in os.walk(self.folder):
|
for dirpath, dirnames, filenames in os.walk(self.folder):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
yield filename
|
yield self.key_filter_out(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return list(self.__iter__())
|
||||||
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
@@ -333,6 +364,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
for key in self:
|
for key in self:
|
||||||
del self[key]
|
del self[key]
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, request=None, folder=None):
|
def __init__(self, request=None, folder=None):
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
self.request = request
|
self.request = request
|
||||||
@@ -387,6 +419,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def clear(self, regex=None):
|
def clear(self, regex=None):
|
||||||
self.initialize()
|
self.initialize()
|
||||||
storage = self.storage
|
storage = self.storage
|
||||||
|
|||||||
+2
-2
@@ -127,7 +127,7 @@ class mybuiltin(object):
|
|||||||
def LOAD(c=None, f='index', args=None, vars=None,
|
def LOAD(c=None, f='index', args=None, vars=None,
|
||||||
extension=None, target=None, ajax=False, ajax_trap=False,
|
extension=None, target=None, ajax=False, ajax_trap=False,
|
||||||
url=None, user_signature=False, timeout=None, times=1,
|
url=None, user_signature=False, timeout=None, times=1,
|
||||||
content='loading...', **attr):
|
content='loading...', post_vars=Storage(), **attr):
|
||||||
""" LOADs a component into the action's document
|
""" LOADs a component into the action's document
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -202,7 +202,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
other_request.args = List(args)
|
other_request.args = List(args)
|
||||||
other_request.vars = vars
|
other_request.vars = vars
|
||||||
other_request.get_vars = vars
|
other_request.get_vars = vars
|
||||||
other_request.post_vars = Storage()
|
other_request.post_vars = post_vars
|
||||||
other_response = Response()
|
other_response = Response()
|
||||||
other_request.env.path_info = '/' + \
|
other_request.env.path_info = '/' + \
|
||||||
'/'.join([request.application, c, f] +
|
'/'.join([request.application, c, f] +
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Read from configuration files easily without hurting performances
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
During development you can load a config file either in .ini or .json
|
||||||
|
format (by default app/private/appconfig.ini or app/private/appconfig.json)
|
||||||
|
The result is a dict holding the configured values. Passing reload=True
|
||||||
|
is meant only for development: in production, leave reload to False and all
|
||||||
|
values will be cached
|
||||||
|
|
||||||
|
from gluon.contrib.appconfig import AppConfig
|
||||||
|
myconfig = AppConfig(path_to_configfile, reload=False)
|
||||||
|
|
||||||
|
print myconfig['db']['uri']
|
||||||
|
|
||||||
|
The returned dict can walk with "dot notation" an arbitrarely nested dict
|
||||||
|
|
||||||
|
print myconfig.take('db.uri')
|
||||||
|
|
||||||
|
You can even pass a cast function, i.e.
|
||||||
|
|
||||||
|
print myconfig.take('auth.expiration', cast=int)
|
||||||
|
|
||||||
|
Once the value has been fetched (and casted) it won't change until the process
|
||||||
|
is restarted (or reload=True is passed).
|
||||||
|
|
||||||
|
"""
|
||||||
|
import thread
|
||||||
|
import os
|
||||||
|
from ConfigParser import SafeConfigParser
|
||||||
|
from gluon import current
|
||||||
|
from gluon.serializers import json_parser
|
||||||
|
|
||||||
|
locker = thread.allocate_lock()
|
||||||
|
|
||||||
|
|
||||||
|
def AppConfig(*args, **vars):
|
||||||
|
|
||||||
|
locker.acquire()
|
||||||
|
reload_ = vars.pop('reload', False)
|
||||||
|
try:
|
||||||
|
instance_name = 'AppConfig_' + current.request.application
|
||||||
|
if reload_ or not hasattr(AppConfig, instance_name):
|
||||||
|
setattr(AppConfig, instance_name, AppConfigLoader(*args, **vars))
|
||||||
|
return getattr(AppConfig, instance_name).settings
|
||||||
|
finally:
|
||||||
|
locker.release()
|
||||||
|
|
||||||
|
|
||||||
|
class AppConfigDict(dict):
|
||||||
|
"""
|
||||||
|
dict that has a .take() method to fetch nested values and puts
|
||||||
|
them into cache
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
dict.__init__(self, *args, **kwargs)
|
||||||
|
self.int_cache = {}
|
||||||
|
|
||||||
|
def take(self, path, cast=None):
|
||||||
|
parts = path.split('.')
|
||||||
|
if path in self.int_cache:
|
||||||
|
return self.int_cache[path]
|
||||||
|
value = self
|
||||||
|
walking = []
|
||||||
|
for part in parts:
|
||||||
|
if part not in value:
|
||||||
|
raise BaseException("%s not in config [%s]" %
|
||||||
|
(part, '-->'.join(walking)))
|
||||||
|
value = value[part]
|
||||||
|
walking.append(part)
|
||||||
|
if cast is None:
|
||||||
|
self.int_cache[path] = value
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = cast(value)
|
||||||
|
self.int_cache[path] = value
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise BaseException("%s can't be converted to %s" %
|
||||||
|
(value, cast))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class AppConfigLoader(object):
|
||||||
|
|
||||||
|
def __init__(self, configfile=None):
|
||||||
|
if not configfile:
|
||||||
|
priv_folder = os.path.join(current.request.folder, 'private')
|
||||||
|
configfile = os.path.join(priv_folder, 'appconfig.ini')
|
||||||
|
if not os.path.isfile(configfile):
|
||||||
|
configfile = os.path.join(priv_folder, 'appconfig.json')
|
||||||
|
if not os.path.isfile(configfile):
|
||||||
|
configfile = None
|
||||||
|
if not configfile or not os.path.isfile(configfile):
|
||||||
|
raise BaseException("Config file not found")
|
||||||
|
self.file = configfile
|
||||||
|
self.ctype = os.path.splitext(configfile)[1][1:]
|
||||||
|
self.settings = None
|
||||||
|
self.read_config()
|
||||||
|
|
||||||
|
def read_config_ini(self):
|
||||||
|
config = SafeConfigParser()
|
||||||
|
config.read(self.file)
|
||||||
|
settings = {}
|
||||||
|
for section in config.sections():
|
||||||
|
settings[section] = {}
|
||||||
|
for option in config.options(section):
|
||||||
|
settings[section][option] = config.get(section, option)
|
||||||
|
self.settings = AppConfigDict(settings)
|
||||||
|
|
||||||
|
def read_config_json(self):
|
||||||
|
with open(self.file, 'r') as c:
|
||||||
|
self.settings = AppConfigDict(json_parser.load(c))
|
||||||
|
|
||||||
|
def read_config(self):
|
||||||
|
if self.settings is None:
|
||||||
|
try:
|
||||||
|
getattr(self, 'read_config_' + self.ctype)()
|
||||||
|
except AttributeError:
|
||||||
|
raise BaseException("Unsupported config file format")
|
||||||
|
return self.settings
|
||||||
@@ -340,4 +340,3 @@ example_policies = {
|
|||||||
'DELETE':{'query':None},
|
'DELETE':{'query':None},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class BrowserID(object):
|
|||||||
auth_info_json = fetch(self.verify_url, data=verify_data)
|
auth_info_json = fetch(self.verify_url, data=verify_data)
|
||||||
j = json.loads(auth_info_json)
|
j = json.loads(auth_info_json)
|
||||||
epoch_time = int(time.time() * 1000) # we need 13 digit epoch time
|
epoch_time = int(time.time() * 1000) # we need 13 digit epoch time
|
||||||
if j["status"] == "okay" and j["audience"] == audience and j['issuer'] == issuer and j['expires'] >= epoch_time:
|
if j["status"] == "okay" and j["audience"] == audience and j['issuer'].endswith(issuer) and j['expires'] >= epoch_time:
|
||||||
return dict(email=j['email'])
|
return dict(email=j['email'])
|
||||||
elif self.on_login_failure:
|
elif self.on_login_failure:
|
||||||
#print "status: ", j["status"]=="okay", j["status"]
|
#print "status: ", j["status"]=="okay", j["status"]
|
||||||
|
|||||||
@@ -252,4 +252,3 @@ class IMAP4(object):
|
|||||||
return Connection()
|
return Connection()
|
||||||
|
|
||||||
IMAP4_SSL = IMAP4
|
IMAP4_SSL = IMAP4
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ class SoapDispatcher(object):
|
|||||||
body.marshall("%s:Fault" % soap_ns, fault, ns=False)
|
body.marshall("%s:Fault" % soap_ns, fault, ns=False)
|
||||||
else:
|
else:
|
||||||
# return normal value
|
# return normal value
|
||||||
res = body.add_child("%sResponse" % name, ns=prefix)
|
res = body.add_child("%sResponse" % name, ns=self.namespace)
|
||||||
if not prefix:
|
if not prefix:
|
||||||
res['xmlns'] = self.namespace # add target namespace
|
res['xmlns'] = self.namespace # add target namespace
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ jQuery(function(){
|
|||||||
<label class="control-label">Card Number</label>
|
<label class="control-label">Card Number</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" size="20" data-stripe="number"
|
<input type="text" size="20" data-stripe="number"
|
||||||
placeholder="4242424242424242"/>
|
placeholder="4242424242424242"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ jQuery(function(){
|
|||||||
<label class="control-label">CVC</label>
|
<label class="control-label">CVC</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" size="4" style="width:80px" data-stripe="cvc"
|
<input type="text" size="4" style="width:80px" data-stripe="cvc"
|
||||||
placeholder="XXX"/>
|
placeholder="XXX"/>
|
||||||
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
|
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,10 +209,10 @@ jQuery(function(){
|
|||||||
<label class="control-label">Expiration</label>
|
<label class="control-label">Expiration</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
|
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
|
||||||
placeholder="MM"/>
|
placeholder="MM"/>
|
||||||
/
|
/
|
||||||
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
|
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
|
||||||
placeholder="YYYY"/>
|
placeholder="YYYY"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Developed by Massimo Di Pierro
|
Developed by Massimo Di Pierro
|
||||||
Released under the web2py license (LGPL)
|
Released under the web2py license (LGPL)
|
||||||
|
|||||||
+3
-1
@@ -550,7 +550,7 @@ class Response(Storage):
|
|||||||
else:
|
else:
|
||||||
attname = filename
|
attname = filename
|
||||||
headers["Content-Disposition"] = \
|
headers["Content-Disposition"] = \
|
||||||
"attachment;filename=%s" % attname
|
'attachment;filename="%s"' % attname
|
||||||
|
|
||||||
if not request:
|
if not request:
|
||||||
request = current.request
|
request = current.request
|
||||||
@@ -630,6 +630,8 @@ class Response(Storage):
|
|||||||
return self.stream(stream, chunk_size=chunk_size, request=request)
|
return self.stream(stream, chunk_size=chunk_size, request=request)
|
||||||
|
|
||||||
def json(self, data, default=None):
|
def json(self, data, default=None):
|
||||||
|
if 'Content-Type' not in self.headers:
|
||||||
|
self.headers['Content-Type'] = 'application/json'
|
||||||
return json(data, default=default or custom_json)
|
return json(data, default=default or custom_json)
|
||||||
|
|
||||||
def xmlrpc(self, request, methods):
|
def xmlrpc(self, request, methods):
|
||||||
|
|||||||
+1
-1
@@ -850,7 +850,7 @@ class DIV(XmlComponent):
|
|||||||
"""
|
"""
|
||||||
components = []
|
components = []
|
||||||
for c in self.components:
|
for c in self.components:
|
||||||
if isinstance(c, allowed_parents):
|
if isinstance(c, (allowed_parents,CAT)):
|
||||||
pass
|
pass
|
||||||
elif wrap_lambda:
|
elif wrap_lambda:
|
||||||
c = wrap_lambda(c)
|
c = wrap_lambda(c)
|
||||||
|
|||||||
+2
-1
@@ -878,6 +878,7 @@ class MapUrlIn(object):
|
|||||||
self.domain_application = None
|
self.domain_application = None
|
||||||
self.domain_controller = None
|
self.domain_controller = None
|
||||||
self.domain_function = None
|
self.domain_function = None
|
||||||
|
self.map_hyphen = base.map_hyphen
|
||||||
arg0 = self.harg0
|
arg0 = self.harg0
|
||||||
if not base.exclusive_domain and base.applications and arg0 in base.applications:
|
if not base.exclusive_domain and base.applications and arg0 in base.applications:
|
||||||
self.application = arg0
|
self.application = arg0
|
||||||
@@ -1256,9 +1257,9 @@ class MapUrlOut(object):
|
|||||||
"Builds a/c/f from components"
|
"Builds a/c/f from components"
|
||||||
acf = ''
|
acf = ''
|
||||||
if self.map_hyphen:
|
if self.map_hyphen:
|
||||||
self.application = self.application.replace('_', '-')
|
|
||||||
self.controller = self.controller.replace('_', '-')
|
self.controller = self.controller.replace('_', '-')
|
||||||
if self.controller != 'static' and not self.controller.startswith('static/'):
|
if self.controller != 'static' and not self.controller.startswith('static/'):
|
||||||
|
self.application = self.application.replace('_', '-')
|
||||||
self.function = self.function.replace('_', '-')
|
self.function = self.function.replace('_', '-')
|
||||||
if not self.omit_application:
|
if not self.omit_application:
|
||||||
acf += '/' + self.application
|
acf += '/' + self.application
|
||||||
|
|||||||
+5
-4
@@ -1850,13 +1850,14 @@ class WSGIWorker(Worker):
|
|||||||
if data:
|
if data:
|
||||||
self.write(data, sections)
|
self.write(data, sections)
|
||||||
|
|
||||||
if self.chunked:
|
if not self.headers_sent:
|
||||||
# If chunked, send our final chunk length
|
|
||||||
self.conn.sendall(b('0\r\n\r\n'))
|
|
||||||
elif not self.headers_sent:
|
|
||||||
# Send headers if the body was empty
|
# Send headers if the body was empty
|
||||||
self.send_headers('', sections)
|
self.send_headers('', sections)
|
||||||
|
|
||||||
|
if self.chunked and self.request_method != 'HEAD':
|
||||||
|
# If chunked, send our final chunk length
|
||||||
|
self.conn.sendall(b('0\r\n\r\n'))
|
||||||
|
|
||||||
# Don't capture exceptions here. The Worker class handles
|
# Don't capture exceptions here. The Worker class handles
|
||||||
# them appropriately.
|
# them appropriately.
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
+34
-20
@@ -87,11 +87,17 @@ if 'WEB2PY_PATH' not in os.environ:
|
|||||||
os.environ['WEB2PY_PATH'] = path
|
os.environ['WEB2PY_PATH'] = path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from gluon.contrib.simplejson import loads, dumps
|
# try external module
|
||||||
except:
|
|
||||||
from simplejson import loads, dumps
|
from simplejson import loads, dumps
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
# try stdlib (Python >= 2.6)
|
||||||
|
from json import loads, dumps
|
||||||
|
except:
|
||||||
|
# fallback to pure-Python module
|
||||||
|
from gluon.contrib.simplejson import loads, dumps
|
||||||
|
|
||||||
IDENTIFIER = "%s#%s" % (socket.gethostname(),os.getpid())
|
IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
|
||||||
|
|
||||||
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
|
logger = logging.getLogger('web2py.scheduler.%s' % IDENTIFIER)
|
||||||
|
|
||||||
@@ -160,6 +166,7 @@ class TaskReport(object):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '<TaskReport: %s>' % self.status
|
return '<TaskReport: %s>' % self.status
|
||||||
|
|
||||||
|
|
||||||
class JobGraph(object):
|
class JobGraph(object):
|
||||||
"""Experimental: with JobGraph you can specify
|
"""Experimental: with JobGraph you can specify
|
||||||
dependencies amongs tasks"""
|
dependencies amongs tasks"""
|
||||||
@@ -170,7 +177,9 @@ class JobGraph(object):
|
|||||||
|
|
||||||
def add_deps(self, task_parent, task_child):
|
def add_deps(self, task_parent, task_child):
|
||||||
"""Creates a dependency between task_parent and task_child"""
|
"""Creates a dependency between task_parent and task_child"""
|
||||||
self.db.scheduler_task_deps.insert(task_parent=task_parent, task_child=task_child, job_name=self.job_name)
|
self.db.scheduler_task_deps.insert(task_parent=task_parent,
|
||||||
|
task_child=task_child,
|
||||||
|
job_name=self.job_name)
|
||||||
|
|
||||||
def validate(self, job_name):
|
def validate(self, job_name):
|
||||||
"""Validates if all tasks job_name can be completed, i.e. there
|
"""Validates if all tasks job_name can be completed, i.e. there
|
||||||
@@ -195,16 +204,18 @@ class JobGraph(object):
|
|||||||
try:
|
try:
|
||||||
rtn = []
|
rtn = []
|
||||||
for k, v in nested_dict.items():
|
for k, v in nested_dict.items():
|
||||||
v.discard(k) # Ignore self dependencies
|
v.discard(k) # Ignore self dependencies
|
||||||
extra_items_in_deps = reduce(set.union, nested_dict.values()) - set(nested_dict.keys())
|
extra_items_in_deps = reduce(set.union, nested_dict.values()) - set(nested_dict.keys())
|
||||||
nested_dict.update(dict((item, set()) for item in extra_items_in_deps))
|
nested_dict.update(dict((item, set()) for item in extra_items_in_deps))
|
||||||
while True:
|
while True:
|
||||||
ordered = set(item for item,dep in nested_dict.items() if not dep)
|
ordered = set(item for item, dep in nested_dict.items() if not dep)
|
||||||
if not ordered:
|
if not ordered:
|
||||||
break
|
break
|
||||||
rtn.append(ordered)
|
rtn.append(ordered)
|
||||||
nested_dict = dict((item, (dep - ordered)) for item, dep in nested_dict.items()
|
nested_dict = dict(
|
||||||
if item not in ordered)
|
(item, (dep - ordered)) for item, dep in nested_dict.items()
|
||||||
|
if item not in ordered
|
||||||
|
)
|
||||||
assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict
|
assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict
|
||||||
db.commit()
|
db.commit()
|
||||||
return rtn
|
return rtn
|
||||||
@@ -212,6 +223,7 @@ class JobGraph(object):
|
|||||||
db.rollback()
|
db.rollback()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def demo_function(*argv, **kwargs):
|
def demo_function(*argv, **kwargs):
|
||||||
""" test function """
|
""" test function """
|
||||||
for i in range(argv[0]):
|
for i in range(argv[0]):
|
||||||
@@ -268,7 +280,7 @@ def executor(queue, task, out):
|
|||||||
def write(self, data):
|
def write(self, data):
|
||||||
self.out_queue.put(data)
|
self.out_queue.put(data)
|
||||||
|
|
||||||
W2P_TASK = Storage({'id' : task.task_id, 'uuid' : task.uuid})
|
W2P_TASK = Storage({'id': task.task_id, 'uuid': task.uuid})
|
||||||
stdout = LogOutput(out)
|
stdout = LogOutput(out)
|
||||||
try:
|
try:
|
||||||
if task.app:
|
if task.app:
|
||||||
@@ -293,7 +305,7 @@ def executor(queue, task, out):
|
|||||||
raise NameError(
|
raise NameError(
|
||||||
"name '%s' not found in scheduler's environment" % f)
|
"name '%s' not found in scheduler's environment" % f)
|
||||||
#Inject W2P_TASK into environment
|
#Inject W2P_TASK into environment
|
||||||
_env.update({'W2P_TASK' : W2P_TASK})
|
_env.update({'W2P_TASK': W2P_TASK})
|
||||||
#Inject W2P_TASK into current
|
#Inject W2P_TASK into current
|
||||||
from gluon import current
|
from gluon import current
|
||||||
current.W2P_TASK = W2P_TASK
|
current.W2P_TASK = W2P_TASK
|
||||||
@@ -357,8 +369,7 @@ class MetaScheduler(threading.Thread):
|
|||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
while p.is_alive() and (
|
while p.is_alive() and (not task.timeout or time.time() - start < task.timeout):
|
||||||
not task.timeout or time.time() - start < task.timeout):
|
|
||||||
if tout:
|
if tout:
|
||||||
try:
|
try:
|
||||||
logger.debug(' partial output saved')
|
logger.debug(' partial output saved')
|
||||||
@@ -568,7 +579,7 @@ class Scheduler(MetaScheduler):
|
|||||||
queue=0,
|
queue=0,
|
||||||
distribution=None,
|
distribution=None,
|
||||||
workers=0)
|
workers=0)
|
||||||
) #dict holding statistics
|
) # dict holding statistics
|
||||||
|
|
||||||
from gluon import current
|
from gluon import current
|
||||||
current._scheduler = self
|
current._scheduler = self
|
||||||
@@ -740,7 +751,7 @@ class Scheduler(MetaScheduler):
|
|||||||
contention and retries `assign_task` after 0.5 seconds
|
contention and retries `assign_task` after 0.5 seconds
|
||||||
"""
|
"""
|
||||||
logger.debug('Assigning tasks...')
|
logger.debug('Assigning tasks...')
|
||||||
db.commit() #db.commit() only for Mysql
|
db.commit() # db.commit() only for Mysql
|
||||||
x = 0
|
x = 0
|
||||||
while x < 10:
|
while x < 10:
|
||||||
try:
|
try:
|
||||||
@@ -761,7 +772,7 @@ class Scheduler(MetaScheduler):
|
|||||||
contention and retries `pop_task` after 0.5 seconds
|
contention and retries `pop_task` after 0.5 seconds
|
||||||
"""
|
"""
|
||||||
db = self.db
|
db = self.db
|
||||||
db.commit() #another nifty db.commit() only for Mysql
|
db.commit() # another nifty db.commit() only for Mysql
|
||||||
x = 0
|
x = 0
|
||||||
while x < 10:
|
while x < 10:
|
||||||
try:
|
try:
|
||||||
@@ -1077,6 +1088,8 @@ class Scheduler(MetaScheduler):
|
|||||||
#build workers as dict of groups
|
#build workers as dict of groups
|
||||||
wkgroups = {}
|
wkgroups = {}
|
||||||
for w in all_workers:
|
for w in all_workers:
|
||||||
|
if w.worker_stats['status'] == 'RUNNING':
|
||||||
|
continue
|
||||||
group_names = w.group_names
|
group_names = w.group_names
|
||||||
for gname in group_names:
|
for gname in group_names:
|
||||||
if gname not in wkgroups:
|
if gname not in wkgroups:
|
||||||
@@ -1090,8 +1103,9 @@ class Scheduler(MetaScheduler):
|
|||||||
db(
|
db(
|
||||||
(st.status.belongs((QUEUED, ASSIGNED))) &
|
(st.status.belongs((QUEUED, ASSIGNED))) &
|
||||||
(st.stop_time < now)
|
(st.stop_time < now)
|
||||||
).update(status=EXPIRED)
|
).update(status=EXPIRED)
|
||||||
|
|
||||||
|
#calculate dependencies
|
||||||
deps_with_no_deps = db(
|
deps_with_no_deps = db(
|
||||||
(sd.can_visit == False) &
|
(sd.can_visit == False) &
|
||||||
(~sd.task_child.belongs(
|
(~sd.task_child.belongs(
|
||||||
@@ -1163,7 +1177,7 @@ class Scheduler(MetaScheduler):
|
|||||||
if not task.task_name:
|
if not task.task_name:
|
||||||
d['task_name'] = task.function_name
|
d['task_name'] = task.function_name
|
||||||
db(
|
db(
|
||||||
(st.id==task.id) &
|
(st.id == task.id) &
|
||||||
(st.status.belongs((QUEUED, ASSIGNED)))
|
(st.status.belongs((QUEUED, ASSIGNED)))
|
||||||
).update(**d)
|
).update(**d)
|
||||||
wkgroups[gname]['workers'][myw]['c'] += 1
|
wkgroups[gname]['workers'][myw]['c'] += 1
|
||||||
@@ -1207,8 +1221,8 @@ class Scheduler(MetaScheduler):
|
|||||||
else:
|
else:
|
||||||
for group in group_names:
|
for group in group_names:
|
||||||
workers = self.db(
|
workers = self.db(
|
||||||
(ws.group_names.contains(group)) &
|
(ws.group_names.contains(group)) &
|
||||||
(~ws.status.belongs(exclusion))
|
(~ws.status.belongs(exclusion))
|
||||||
)._select(ws.id, limitby=(0,limit))
|
)._select(ws.id, limitby=(0,limit))
|
||||||
self.db(ws.id.belongs(workers)).update(status=action)
|
self.db(ws.id.belongs(workers)).update(status=action)
|
||||||
|
|
||||||
@@ -1339,7 +1353,7 @@ class Scheduler(MetaScheduler):
|
|||||||
**dict(orderby=orderby,
|
**dict(orderby=orderby,
|
||||||
left=left,
|
left=left,
|
||||||
limitby=(0, 1))
|
limitby=(0, 1))
|
||||||
).first()
|
).first()
|
||||||
if row and output:
|
if row and output:
|
||||||
row.result = row.scheduler_run.run_result and \
|
row.result = row.scheduler_run.run_result and \
|
||||||
loads(row.scheduler_run.run_result,
|
loads(row.scheduler_run.run_result,
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ def custom_json(o):
|
|||||||
return str(o)
|
return str(o)
|
||||||
elif isinstance(o, XmlComponent):
|
elif isinstance(o, XmlComponent):
|
||||||
return str(o)
|
return str(o)
|
||||||
|
elif isinstance(o, set):
|
||||||
|
return list(o)
|
||||||
elif hasattr(o, 'as_list') and callable(o.as_list):
|
elif hasattr(o, 'as_list') and callable(o.as_list):
|
||||||
return o.as_list()
|
return o.as_list()
|
||||||
elif hasattr(o, 'as_dict') and callable(o.as_dict):
|
elif hasattr(o, 'as_dict') and callable(o.as_dict):
|
||||||
|
|||||||
+15
-5
@@ -1988,9 +1988,9 @@ class SQLFORM(FORM):
|
|||||||
buttonback='icon leftarrow icon-arrow-left glyphicon glyphicon-arrow-left',
|
buttonback='icon leftarrow icon-arrow-left glyphicon glyphicon-arrow-left',
|
||||||
buttonexport='icon downarrow icon-download glyphicon glyphicon-download',
|
buttonexport='icon downarrow icon-download glyphicon glyphicon-download',
|
||||||
buttondelete='icon trash icon-trash glyphicon glyphicon-trash',
|
buttondelete='icon trash icon-trash glyphicon glyphicon-trash',
|
||||||
buttonedit='icon pen icon-pencil glyphicon glyphicon-arrow-pencil',
|
buttonedit='icon pen icon-pencil glyphicon glyphicon-pencil',
|
||||||
buttontable='icon rightarrow icon-arrow-right glyphicon glyphicon-arrow-right',
|
buttontable='icon rightarrow icon-arrow-right glyphicon glyphicon-arrow-right',
|
||||||
buttonview='icon magnifier icon-zoom-in glyphicon glyphicon-arrow-zoom-in',
|
buttonview='icon magnifier icon-zoom-in glyphicon glyphicon-zoom-in',
|
||||||
)
|
)
|
||||||
elif not isinstance(ui, dict):
|
elif not isinstance(ui, dict):
|
||||||
raise RuntimeError('SQLFORM.grid ui argument must be a dictionary')
|
raise RuntimeError('SQLFORM.grid ui argument must be a dictionary')
|
||||||
@@ -2333,8 +2333,12 @@ class SQLFORM(FORM):
|
|||||||
#fields but not virtual fields
|
#fields but not virtual fields
|
||||||
sfields = reduce(lambda a, b: a + b,
|
sfields = reduce(lambda a, b: a + b,
|
||||||
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
|
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
|
||||||
dbset = dbset(SQLFORM.build_query(
|
#use custom_query using searchable
|
||||||
sfields, keywords))
|
if callable(searchable):
|
||||||
|
dbset = dbset(searchable(sfields, keywords))
|
||||||
|
else:
|
||||||
|
dbset = dbset(SQLFORM.build_query(
|
||||||
|
sfields, keywords))
|
||||||
rows = dbset.select(left=left, orderby=orderby,
|
rows = dbset.select(left=left, orderby=orderby,
|
||||||
cacheable=True, *selectable_columns)
|
cacheable=True, *selectable_columns)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@@ -2349,7 +2353,9 @@ class SQLFORM(FORM):
|
|||||||
# expcolumns is all cols to be exported including virtual fields
|
# expcolumns is all cols to be exported including virtual fields
|
||||||
rows.colnames = expcolumns
|
rows.colnames = expcolumns
|
||||||
oExp = clazz(rows)
|
oExp = clazz(rows)
|
||||||
filename = '.'.join(('rows', oExp.file_ext))
|
export_filename = \
|
||||||
|
request.vars.get('_export_filename') or 'rows'
|
||||||
|
filename = '.'.join((export_filename, oExp.file_ext))
|
||||||
response.headers['Content-Type'] = oExp.content_type
|
response.headers['Content-Type'] = oExp.content_type
|
||||||
response.headers['Content-Disposition'] = \
|
response.headers['Content-Disposition'] = \
|
||||||
'attachment;filename=' + filename + ';'
|
'attachment;filename=' + filename + ';'
|
||||||
@@ -2386,6 +2392,8 @@ class SQLFORM(FORM):
|
|||||||
spanel_id = '%s_query_fields' % prefix
|
spanel_id = '%s_query_fields' % prefix
|
||||||
sfields_id = '%s_query_panel' % prefix
|
sfields_id = '%s_query_panel' % prefix
|
||||||
skeywords_id = '%s_keywords' % prefix
|
skeywords_id = '%s_keywords' % prefix
|
||||||
|
## hidden fields to presever keywords in url after the submit
|
||||||
|
hidden_fields = [INPUT(_type='hidden', _value=value, _name=key) for key, value in request.get_vars.items() if key not in ['keywords', 'page']]
|
||||||
search_widget = lambda sfield, url: CAT(FORM(
|
search_widget = lambda sfield, url: CAT(FORM(
|
||||||
INPUT(_name='keywords', _value=keywords,
|
INPUT(_name='keywords', _value=keywords,
|
||||||
_id=skeywords_id,_class='form-control',
|
_id=skeywords_id,_class='form-control',
|
||||||
@@ -2394,7 +2402,9 @@ class SQLFORM(FORM):
|
|||||||
INPUT(_type='submit', _value=T('Search'), _class="btn btn-default"),
|
INPUT(_type='submit', _value=T('Search'), _class="btn btn-default"),
|
||||||
INPUT(_type='submit', _value=T('Clear'), _class="btn btn-default",
|
INPUT(_type='submit', _value=T('Clear'), _class="btn btn-default",
|
||||||
_onclick="jQuery('#%s').val('');" % skeywords_id),
|
_onclick="jQuery('#%s').val('');" % skeywords_id),
|
||||||
|
*hidden_fields,
|
||||||
_method="GET", _action=url), search_menu)
|
_method="GET", _action=url), search_menu)
|
||||||
|
# TODO vars from the url should be removed, they are not used by the submit
|
||||||
form = search_widget and search_widget(sfields, url()) or ''
|
form = search_widget and search_widget(sfields, url()) or ''
|
||||||
console.append(add)
|
console.append(add)
|
||||||
console.append(form)
|
console.append(form)
|
||||||
|
|||||||
+1
-1
@@ -195,7 +195,7 @@ class Messages(Settings):
|
|||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
value = self[key]
|
value = self[key]
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return str(self.T(value))
|
return self.T(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
class FastStorage(dict):
|
class FastStorage(dict):
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from test_utils import *
|
|||||||
from test_contribs import *
|
from test_contribs import *
|
||||||
from test_web import *
|
from test_web import *
|
||||||
from test_dal import *
|
from test_dal import *
|
||||||
|
from test_tools import *
|
||||||
|
|
||||||
if sys.version[:3] == '2.7':
|
if sys.version[:3] == '2.7':
|
||||||
from test_old_doctests import *
|
from test_old_doctests import *
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class TestCache(unittest.TestCase):
|
|||||||
def testCacheInRam(self):
|
def testCacheInRam(self):
|
||||||
|
|
||||||
# defaults to mode='http'
|
# defaults to mode='http'
|
||||||
|
|
||||||
cache = CacheInRam()
|
cache = CacheInRam()
|
||||||
self.assertEqual(cache('a', lambda: 1, 0), 1)
|
self.assertEqual(cache('a', lambda: 1, 0), 1)
|
||||||
self.assertEqual(cache('a', lambda: 2, 100), 1)
|
self.assertEqual(cache('a', lambda: 2, 100), 1)
|
||||||
@@ -66,7 +65,6 @@ class TestCache(unittest.TestCase):
|
|||||||
def testCacheOnDisk(self):
|
def testCacheOnDisk(self):
|
||||||
|
|
||||||
# defaults to mode='http'
|
# defaults to mode='http'
|
||||||
|
|
||||||
s = Storage({'application': 'admin',
|
s = Storage({'application': 'admin',
|
||||||
'folder': 'applications/admin'})
|
'folder': 'applications/admin'})
|
||||||
cache = CacheOnDisk(s)
|
cache = CacheOnDisk(s)
|
||||||
@@ -102,8 +100,14 @@ class TestCache(unittest.TestCase):
|
|||||||
self.assertEqual(prefix('a', lambda: 2, 100), 1)
|
self.assertEqual(prefix('a', lambda: 2, 100), 1)
|
||||||
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
|
self.assertEqual(cache.ram('prefixa', lambda: 2, 100), 1)
|
||||||
|
|
||||||
|
def testRegex(self):
|
||||||
|
cache = CacheInRam()
|
||||||
|
self.assertEqual(cache('a1', lambda: 1, 0), 1)
|
||||||
|
self.assertEqual(cache('a2', lambda: 2, 100), 2)
|
||||||
|
cache.clear(regex=r'a*')
|
||||||
|
self.assertEqual(cache('a1', lambda: 2, 0), 2)
|
||||||
|
self.assertEqual(cache('a2', lambda: 3, 100), 3)
|
||||||
|
return
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
setUpModule() # pre-python-2.7
|
setUpModule() # pre-python-2.7
|
||||||
|
|||||||
@@ -4,14 +4,24 @@
|
|||||||
""" Unit tests for contribs """
|
""" Unit tests for contribs """
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import os
|
||||||
from fix_path import fix_sys_path
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
fix_sys_path(__file__)
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
|
from gluon.storage import Storage
|
||||||
|
import gluon.contrib.fpdf as fpdf
|
||||||
|
import gluon.contrib.pyfpdf as pyfpdf
|
||||||
|
from gluon.contrib.appconfig import AppConfig
|
||||||
|
|
||||||
from utils import md5_hash
|
|
||||||
import contrib.fpdf as fpdf
|
def setUpModule():
|
||||||
import contrib.pyfpdf as pyfpdf
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule():
|
||||||
|
if os.path.isfile('appconfig.json'):
|
||||||
|
os.unlink('appconfig.json')
|
||||||
|
|
||||||
|
|
||||||
class TestContribs(unittest.TestCase):
|
class TestContribs(unittest.TestCase):
|
||||||
@@ -35,6 +45,28 @@ class TestContribs(unittest.TestCase):
|
|||||||
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
|
self.assertTrue(fpdf.FPDF_VERSION in pdf_out, 'version string')
|
||||||
self.assertTrue('hello world' in pdf_out, 'sample message')
|
self.assertTrue('hello world' in pdf_out, 'sample message')
|
||||||
|
|
||||||
|
def test_appconfig(self):
|
||||||
|
"""
|
||||||
|
Test for the appconfig module
|
||||||
|
"""
|
||||||
|
from gluon import current
|
||||||
|
s = Storage({'application': 'admin',
|
||||||
|
'folder': 'applications/admin'})
|
||||||
|
current.request = s
|
||||||
|
simple_config = '{"config1" : "abc", "config2" : "bcd", "config3" : { "key1" : 1, "key2" : 2} }'
|
||||||
|
with open('appconfig.json', 'w') as g:
|
||||||
|
g.write(simple_config)
|
||||||
|
myappconfig = AppConfig('appconfig.json')
|
||||||
|
self.assertEqual(myappconfig['config1'], 'abc')
|
||||||
|
self.assertEqual(myappconfig['config2'], 'bcd')
|
||||||
|
self.assertEqual(myappconfig.take('config1'), 'abc')
|
||||||
|
self.assertEqual(myappconfig.take('config3.key1', cast=str), '1')
|
||||||
|
# once parsed, can't be casted to other types
|
||||||
|
self.assertEqual(myappconfig.take('config3.key1', cast=int), '1')
|
||||||
|
|
||||||
|
self.assertEqual(myappconfig.take('config3.key2'), 2)
|
||||||
|
|
||||||
|
current.request = {}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Unit tests for gluon.tools
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
if sys.version < "2.7":
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
|
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||||
|
|
||||||
|
from gluon.dal import DAL, Field
|
||||||
|
from dal.objects import Table
|
||||||
|
from tools import Auth
|
||||||
|
from gluon.globals import Request, Response, Session
|
||||||
|
from storage import Storage
|
||||||
|
from languages import translator
|
||||||
|
from gluon.http import HTTP
|
||||||
|
|
||||||
|
python_version = sys.version[:3]
|
||||||
|
IS_IMAP = "imap" in DEFAULT_URI
|
||||||
|
|
||||||
|
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
|
||||||
|
class testAuth(unittest.TestCase):
|
||||||
|
|
||||||
|
def testRun(self):
|
||||||
|
# setup
|
||||||
|
request = Request(env={})
|
||||||
|
request.application = 'a'
|
||||||
|
request.controller = 'c'
|
||||||
|
request.function = 'f'
|
||||||
|
request.folder = 'applications/admin'
|
||||||
|
response = Response()
|
||||||
|
session = Session()
|
||||||
|
T = translator('', 'en')
|
||||||
|
session.connect(request, response)
|
||||||
|
from gluon.globals import current
|
||||||
|
current.request = request
|
||||||
|
current.response = response
|
||||||
|
current.session = session
|
||||||
|
current.T = T
|
||||||
|
db = DAL(DEFAULT_URI, check_reserved=['all'])
|
||||||
|
auth = Auth(db)
|
||||||
|
auth.define_tables(username=True, signature=False)
|
||||||
|
self.assertTrue('auth_user' in db)
|
||||||
|
self.assertTrue('auth_group' in db)
|
||||||
|
self.assertTrue('auth_membership' in db)
|
||||||
|
self.assertTrue('auth_permission' in db)
|
||||||
|
self.assertTrue('auth_event' in db)
|
||||||
|
db.define_table('t0', Field('tt'), auth.signature)
|
||||||
|
auth.enable_record_versioning(db)
|
||||||
|
self.assertTrue('t0_archive' in db)
|
||||||
|
for f in ['login', 'register', 'retrieve_password',
|
||||||
|
'retrieve_username']:
|
||||||
|
html_form = getattr(auth, f)().xml()
|
||||||
|
self.assertTrue('name="_formkey"' in html_form)
|
||||||
|
|
||||||
|
for f in ['logout', 'verify_email', 'reset_password',
|
||||||
|
'change_password', 'profile', 'groups']:
|
||||||
|
self.assertRaisesRegexp(HTTP, "303*", getattr(auth, f))
|
||||||
|
|
||||||
|
self.assertRaisesRegexp(HTTP, "401*", auth.impersonate)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for t in ['t0_archive', 't0', 'auth_cas', 'auth_event',
|
||||||
|
'auth_membership', 'auth_permission', 'auth_group',
|
||||||
|
'auth_user']:
|
||||||
|
db[t].drop()
|
||||||
|
except SyntaxError as e:
|
||||||
|
# GAE doesn't support drop
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
+9
-5
@@ -136,6 +136,7 @@ class Mail(object):
|
|||||||
mail.settings.server
|
mail.settings.server
|
||||||
mail.settings.sender
|
mail.settings.sender
|
||||||
mail.settings.login
|
mail.settings.login
|
||||||
|
mail.settings.timeout = 60 # seconds (default)
|
||||||
|
|
||||||
When server is 'logging', email is logged but not sent (debug mode)
|
When server is 'logging', email is logged but not sent (debug mode)
|
||||||
|
|
||||||
@@ -265,6 +266,7 @@ class Mail(object):
|
|||||||
settings.sender = sender
|
settings.sender = sender
|
||||||
settings.login = login
|
settings.login = login
|
||||||
settings.tls = tls
|
settings.tls = tls
|
||||||
|
settings.timeout = 60 # seconds
|
||||||
settings.hostname = None
|
settings.hostname = None
|
||||||
settings.ssl = False
|
settings.ssl = False
|
||||||
settings.cipher_type = None
|
settings.cipher_type = None
|
||||||
@@ -787,10 +789,11 @@ class Mail(object):
|
|||||||
subject=subject, body=text, **xcc)
|
subject=subject, body=text, **xcc)
|
||||||
else:
|
else:
|
||||||
smtp_args = self.settings.server.split(':')
|
smtp_args = self.settings.server.split(':')
|
||||||
|
kwargs = dict(timeout = self.settings.timeout)
|
||||||
if self.settings.ssl:
|
if self.settings.ssl:
|
||||||
server = smtplib.SMTP_SSL(*smtp_args)
|
server = smtplib.SMTP_SSL(*smtp_args, **kwargs)
|
||||||
else:
|
else:
|
||||||
server = smtplib.SMTP(*smtp_args)
|
server = smtplib.SMTP(*smtp_args, **kwargs)
|
||||||
if self.settings.tls and not self.settings.ssl:
|
if self.settings.tls and not self.settings.ssl:
|
||||||
server.ehlo(self.settings.hostname)
|
server.ehlo(self.settings.hostname)
|
||||||
server.starttls()
|
server.starttls()
|
||||||
@@ -842,6 +845,7 @@ class Recaptcha(DIV):
|
|||||||
comment = '',
|
comment = '',
|
||||||
ajax=False
|
ajax=False
|
||||||
):
|
):
|
||||||
|
request = request or current.request
|
||||||
self.request_vars = request and request.vars or current.request.vars
|
self.request_vars = request and request.vars or current.request.vars
|
||||||
self.remote_addr = request.env.remote_addr
|
self.remote_addr = request.env.remote_addr
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
@@ -1257,7 +1261,8 @@ class Auth(object):
|
|||||||
def __init__(self, environment=None, db=None, mailer=True,
|
def __init__(self, environment=None, db=None, mailer=True,
|
||||||
hmac_key=None, controller='default', function='user',
|
hmac_key=None, controller='default', function='user',
|
||||||
cas_provider=None, signature=True, secure=False,
|
cas_provider=None, signature=True, secure=False,
|
||||||
csrf_prevention=True, propagate_extension=None):
|
csrf_prevention=True, propagate_extension=None,
|
||||||
|
url_index=None):
|
||||||
|
|
||||||
## next two lines for backward compatibility
|
## next two lines for backward compatibility
|
||||||
if not db and environment and isinstance(environment, DAL):
|
if not db and environment and isinstance(environment, DAL):
|
||||||
@@ -1294,7 +1299,7 @@ class Auth(object):
|
|||||||
del session.auth
|
del session.auth
|
||||||
# ## what happens after login?
|
# ## what happens after login?
|
||||||
|
|
||||||
url_index = URL(controller, 'index')
|
url_index = url_index or URL(controller, 'index')
|
||||||
url_login = URL(controller, function, args='login',
|
url_login = URL(controller, function, args='login',
|
||||||
extension = propagate_extension)
|
extension = propagate_extension)
|
||||||
# ## what happens after registration?
|
# ## what happens after registration?
|
||||||
@@ -6164,4 +6169,3 @@ class Config(object):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|
||||||
|
|||||||
@@ -350,4 +350,3 @@ def getipaddrinfo(host):
|
|||||||
and isinstance(addrinfo[4][0], basestring)]
|
and isinstance(addrinfo[4][0], basestring)]
|
||||||
except socket.error:
|
except socket.error:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@@ -110,4 +110,3 @@ def main():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@@ -218,4 +218,3 @@ class LinuxService(ServiceBase):
|
|||||||
else:
|
else:
|
||||||
cmd = env['update-rc.d']
|
cmd = env['update-rc.d']
|
||||||
return [cmd, self.name, 'remove']
|
return [cmd, self.name, 'remove']
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user