Compare commits

..
40 Commits
Author SHA1 Message Date
mdipierro afdb028070 R-2.6.4 2013-09-21 20:42:19 -05:00
mdipierro 80d4615f32 R-2.6.4 2013-09-21 20:24:31 -05:00
mdipierro 5a12a7fb31 manually committed pull 221, update vars only if FieldStorage holds actually something, thanks Niphlod 2013-09-21 18:15:27 -05:00
mdipierro 17c10b8a45 things got out of sync 2013-09-20 21:28:26 -05:00
mdipierro f8e2c8e319 new validator IS_LIST_OF_EMAILS 2013-09-20 21:17:05 -05:00
mdipierro 9ea82fdaaf fixed issue 1686:admin editor files menu hidden when codemirror is in fullscreen mode, thanks Paolo Caruccio 2013-09-20 20:57:29 -05:00
mdipierro 3d01a83c85 Merge pull request #220 from bunnyhugdev/master
Edited cookie handling code when using a db to store sessions
2013-09-20 12:51:49 -07:00
Joel Rathgaber 13f8b1a10c Edited cookie handling code when using a db to store sessions
- Session id cookie is always sent back to client in response
- When no cookie is sent in as a request, a new session id is created
2013-09-20 13:29:50 -06:00
Massimo f78e172eda fixed issue 1673, propagation for format in archive tables 2013-09-20 12:47:15 -05:00
mdipierro bd452f90b2 session._fixup_before_save 2013-09-20 08:36:52 -05:00
mdipierro 2b1f150f02 relaxed REST conditions 2013-09-19 22:18:45 -05:00
mdipierro e7a9457249 fixed problem with widget and list:string 2013-09-19 22:03:37 -05:00
mdipierro d3ab2e73d5 support for driver mysql.connector, thank you Josiel Santos 2013-09-19 21:44:12 -05:00
mdipierro 860f40ca63 added support for 3letter language files, issue 1680, thanks Fran 2013-09-19 21:33:31 -05:00
mdipierro 59a7db56a4 fixed major problem with persistance of session.flash 2013-09-19 20:51:11 -05:00
mdipierro 01e5107a96 possible fix to issues 1682, REST and DELETE 2013-09-19 18:33:27 -05:00
mdipierro 8765dbbe0f an attempt to fix 1685 2013-09-19 18:26:35 -05:00
mdipierro d125b0b95b expires can be str or datetime 2013-09-19 18:03:35 -05:00
mdipierro 1d2fc4b8f5 simpler logic in parse POST but not functional difference 2013-09-19 17:47:29 -05:00
mdipierro 146ea115bf Merge pull request #219 from niphlod/enhancement/ajax_page_notarget
fixed issue when target is none. removed useless file in admin/static/js
2013-09-19 14:44:03 -07:00
niphlod 4f44188997 fixed issue when target is not none. removed useless file in admin/static/js 2013-09-19 21:52:45 +02:00
mdipierro ac5fb303eb Merge pull request #218 from mpranjic/masterfix
startswith, endswith and contains for upload fields
2013-09-19 12:10:07 -07:00
mdipierro 31a14997f8 Merge pull request #216 from ilvalle/ajax_title
Set document title when an ajax fragment has got a <title> element
2013-09-19 12:09:01 -07:00
mpranjic 4cd4ff2c5e startswith, endswith and contains for upload fields 2013-09-19 10:58:23 +02:00
Massimo 26ef508966 added some missing files, is admin/static/js/jquery-1.10.0.min.map correct? 2013-09-18 14:37:28 -05:00
Paolo 28b4badb9a concatenation of filter and find for a better <title> discovery 2013-09-18 17:33:54 +02:00
mdipierro 8e2e2420b0 fixed style of edit in admin 2013-09-18 10:18:31 -05:00
mdipierro 67349627f4 Merge pull request #214 from niphlod/fix/admin_emmet
fixes keyboard shortcuts in admin when modifying js or css files
2013-09-18 08:05:49 -07:00
mdipierro cfe825f94a Merge pull request #213 from spametki/master
Fix imap insert multipart detection
2013-09-18 08:05:05 -07:00
mdipierro 53e79915fa skip virtual fields on AttributeError 2013-09-18 08:29:31 -05:00
ilvalle 9fb5f688ed Set document title if an ajax fragment has a <title> element 2013-09-18 09:15:06 +02:00
mdipierro c2cdae0615 new setup_exe.py, thanks Niphlod 2013-09-17 08:31:01 -05:00
mdipierro 2516bb59a1 fixed issue 1679:auth.navbar() duplicates first link if mode is 'default', thanks wkl 2013-09-16 08:55:43 -05:00
mdipierro 13c78fae58 R-2.6.3 2013-09-15 12:00:13 -05:00
mdipierro 91c0a31800 R-2.6.3 2013-09-15 11:51:01 -05:00
mdipierro 277137c8e6 R-2.6.3 2013-09-15 11:48:32 -05:00
mdipierro 4556a355a2 fixed a problem with CRYPT password length 2013-09-15 11:46:46 -05:00
mdipierro 7dafb07438 added a extra level of protection for long passwords, even if IS_LENGTH validator is missing 2013-09-15 09:40:15 -05:00
niphlod 3c8e7a1364 fixes keyboard shortcuts in admin when modifying js or css files 2013-09-15 11:23:58 +02:00
spametki 921cd46c10 Fix imap insert multipart detection 2013-09-14 16:03:41 -03:00
24 changed files with 301 additions and 122 deletions
+3 -1
View File
@@ -1,4 +1,4 @@
## 2.6.1 ## 2.6.1 - 2.6.4
Attention all users: For pre 2.6 applications to work with web2py >=2.6, you must copy static/js/web2py.js, controllers/appadmin.py, and views/appadmin.html from the welcome app to your own apps (all of them). Attention all users: For pre 2.6 applications to work with web2py >=2.6, you must copy static/js/web2py.js, controllers/appadmin.py, and views/appadmin.html from the welcome app to your own apps (all of them).
@@ -38,6 +38,8 @@ Attention MySQL users: The length of string fields changed from 255 to 512 bytes
- speedup for define_table, thanks Michele - speedup for define_table, thanks Michele
- settings.cfg to admin, thanks Paolo - settings.cfg to admin, thanks Paolo
- many bugs fixed, thanks Niphlod, Michele, Roberto, Jonathan, and many others - many bugs fixed, thanks Niphlod, Michele, Roberto, Jonathan, and many others
- 2.6,3 specifically fixed a possible DoS vulnerability
- 2.6.4 specifically fixes major problem introduced in 2.6.1 with session logic
## 2.5.2 ## 2.5.2
+1 -1
View File
@@ -30,7 +30,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.6.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.6.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
View File
@@ -1 +1 @@
Version 2.6.2-stable+timestamp.2013.09.13.17.43.10 Version 2.6.4-stable+timestamp.2013.09.21.20.41.28
+1 -1
View File
@@ -108,7 +108,7 @@ def index():
if session.authorized: if session.authorized:
redirect(send) redirect(send)
elif request.vars.password: elif request.vars.password:
if verify_password(request.vars.password): if verify_password(request.vars.password[:1024]):
session.authorized = True session.authorized = True
login_record(True) login_record(True)
+54 -1
View File
@@ -7,6 +7,7 @@
'%s %%{row} updated': '%s filas actualizadas', '%s %%{row} updated': '%s filas actualizadas',
'%Y-%m-%d': '%Y-%m-%d', '%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'(requires internet access, experimental)': '(requires internet access, 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\x01Searching: **%s** %%{file}': 'Searching: **%s** files', '@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
@@ -32,6 +33,7 @@
'application "%s" uninstalled': 'aplicación "%s" desinstalada', 'application "%s" uninstalled': 'aplicación "%s" desinstalada',
'application compiled': 'aplicación compilada', 'application compiled': 'aplicación compilada',
'application is compiled and cannot be designed': 'la aplicación está compilada y no puede ser modificada', 'application is compiled and cannot be designed': 'la aplicación está compilada y no puede ser modificada',
'Application name:': 'Application name:',
'are not used': 'are not used', 'are not used': 'are not used',
'are not used yet': 'are not used yet', 'are not used yet': 'are not used yet',
'Are you sure you want to delete file "%s"?': '¿Está seguro que desea eliminar el archivo "%s"?', 'Are you sure you want to delete file "%s"?': '¿Está seguro que desea eliminar el archivo "%s"?',
@@ -47,6 +49,7 @@
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENCION: NO EJECUTE VARIAS PRUEBAS SIMULTANEAMENTE, NO SON THREAD SAFE.', '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!': 'ATENCION: no puede modificar la aplicación que se ejecuta!', 'ATTENTION: you cannot edit the running application!': 'ATENCION: no puede modificar la aplicación que se ejecuta!',
'Autocomplete': 'Autocomplete', 'Autocomplete': 'Autocomplete',
'Autocomplete Python Code': 'Autocomplete Python Code',
'Available databases and tables': 'Bases de datos y tablas disponibles', 'Available databases and tables': 'Bases de datos y tablas disponibles',
'back': 'atrás', 'back': 'atrás',
'breakpoint': 'breakpoint', 'breakpoint': 'breakpoint',
@@ -54,14 +57,17 @@
'browse': 'buscar', 'browse': 'buscar',
'cache': 'cache', 'cache': 'cache',
'cache, errors and sessions cleaned': 'cache, errores y sesiones eliminados', 'cache, errors and sessions cleaned': 'cache, errores y sesiones eliminados',
'can be a git repo': 'can be a git repo',
'Cannot be empty': 'No puede estar vacío', 'Cannot be empty': 'No puede estar vacío',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se puede compilar: hay errores en su aplicación. Depure, corrija errores y vuelva a intentarlo.', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se puede compilar: hay errores en su aplicación. Depure, corrija errores y vuelva a intentarlo.',
'Cannot compile: there are errors in your app:': 'No se puede compilar: hay errores en su aplicación:', 'Cannot compile: there are errors in your app:': 'No se puede compilar: hay errores en su aplicación:',
'cannot create file': 'no es posible crear archivo', 'cannot create file': 'no es posible crear archivo',
'cannot upload file "%(filename)s"': 'no es posible subir archivo "%(filename)s"', 'cannot upload file "%(filename)s"': 'no es posible subir archivo "%(filename)s"',
'Change admin password': 'cambie contraseña admin', 'Change admin password': 'cambie contraseña admin',
'change editor settings': 'change editor settings',
'Change Password': 'Cambie Contraseña', 'Change Password': 'Cambie Contraseña',
'check all': 'marcar todos', 'check all': 'marcar todos',
'Check for upgrades': 'Check for upgrades',
'Check to delete': 'Marque para eliminar', 'Check to delete': 'Marque para eliminar',
'Checking for upgrades...': 'Buscando actulizaciones...', 'Checking for upgrades...': 'Buscando actulizaciones...',
'Clean': 'limpiar', 'Clean': 'limpiar',
@@ -91,6 +97,7 @@
'Current request': 'Solicitud en curso', 'Current request': 'Solicitud en curso',
'Current response': 'Respuesta en curso', 'Current response': 'Respuesta en curso',
'Current session': 'Sesión en curso', 'Current session': 'Sesión en curso',
'currently running': 'currently running',
'currently saved or': 'actualmente guardado o', 'currently saved or': 'actualmente guardado o',
'customize me!': 'Adaptame!', 'customize me!': 'Adaptame!',
'data uploaded': 'datos subidos', 'data uploaded': 'datos subidos',
@@ -107,14 +114,21 @@
'delete plugin': 'eliminar plugin', 'delete plugin': 'eliminar plugin',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)', 'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Delete:': 'Elimine:', 'Delete:': 'Elimine:',
'Deploy': 'Deploy',
'Deploy on Google App Engine': 'Instale en Google App Engine', 'Deploy on Google App Engine': 'Instale en Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Description': 'Descripción', 'Description': 'Descripción',
'design': 'modificar', 'design': 'modificar',
'DESIGN': 'DISEÑO', 'DESIGN': 'DISEÑO',
'Design for': 'Diseño para', 'Design for': 'Diseño para',
'Detailed traceback description': 'Detailed traceback description',
'details': 'details',
'direction: ltr': 'direction: ltr', 'direction: ltr': 'direction: ltr',
'Disable': 'Disable',
'docs': 'docs', 'docs': 'docs',
'done!': 'listo!', 'done!': 'listo!',
'Download': 'Download',
'download files via http:': 'download files via http:',
'download layouts': 'download layouts', 'download layouts': 'download layouts',
'download plugins': 'download plugins', 'download plugins': 'download plugins',
'E-mail': 'Correo electrónico', 'E-mail': 'Correo electrónico',
@@ -122,17 +136,25 @@
'Edit': 'editar', 'Edit': 'editar',
'Edit application': 'Editar aplicación', 'Edit application': 'Editar aplicación',
'edit controller': 'editar controlador', 'edit controller': 'editar controlador',
'edit controller:': 'edit controller:',
'Edit current record': 'Edite el registro actual', 'Edit current record': 'Edite el registro actual',
'Edit Profile': 'Editar Perfil', 'Edit Profile': 'Editar Perfil',
'edit views:': 'editar vistas:', 'edit views:': 'editar vistas:',
'Editing file': 'Editando archivo', 'Editing file': 'Editando archivo',
'Editing file "%s"': 'Editando archivo "%s"', 'Editing file "%s"': 'Editando archivo "%s"',
'Editing Language file': 'Editando archivo de lenguaje', 'Editing Language file': 'Editando archivo de lenguaje',
'Editing myclientapi': 'Editing myclientapi',
'Editing myemail': 'Editing myemail',
'Editing rbare': 'Editing rbare',
'Editing ul': 'Editing ul',
'Enterprise Web Framework': 'Armazón Empresarial para Internet', 'Enterprise Web Framework': 'Armazón Empresarial para Internet',
'Error': 'Error', 'Error': 'Error',
'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"', 'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'errores', 'Errors': 'errores',
'Exception instance attributes': 'Atributos de la instancia de Excepción', 'Exception instance attributes': 'Atributos de la instancia de Excepción',
'Expand Abbreviation': 'Expand Abbreviation',
'export as csv file': 'exportar como archivo CSV', 'export as csv file': 'exportar como archivo CSV',
'exposes': 'expone', 'exposes': 'expone',
'exposes:': 'exposes:', 'exposes:': 'exposes:',
@@ -154,6 +176,7 @@
'Find Next': 'Find Next', 'Find Next': 'Find Next',
'Find Previous': 'Find Previous', 'Find Previous': 'Find Previous',
'First name': 'Nombre', 'First name': 'Nombre',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].', 'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].',
'Globals##debug': 'Globals', 'Globals##debug': 'Globals',
'graph model': 'graph model', 'graph model': 'graph model',
@@ -166,6 +189,7 @@
'includes': 'incluye', 'includes': 'incluye',
'insert new': 'inserte nuevo', 'insert new': 'inserte nuevo',
'insert new %s': 'inserte nuevo %s', 'insert new %s': 'inserte nuevo %s',
'inspect attributes': 'inspect attributes',
'Install': 'instalar', 'Install': 'instalar',
'Installed applications': 'Aplicaciones instaladas', 'Installed applications': 'Aplicaciones instaladas',
'Interaction at %s line %s': 'Interaction at %s line %s', 'Interaction at %s line %s': 'Interaction at %s line %s',
@@ -175,10 +199,12 @@
'Invalid action': 'Acción inválida', 'Invalid action': 'Acción inválida',
'Invalid email': 'Correo inválido', 'Invalid email': 'Correo inválido',
'invalid password': 'contraseña inválida', 'invalid password': 'contraseña inválida',
'invalid password.': 'invalid password.',
'Invalid Query': 'Consulta inválida', 'Invalid Query': 'Consulta inválida',
'invalid request': 'solicitud inválida', 'invalid request': 'solicitud inválida',
'invalid ticket': 'tiquete inválido', 'invalid ticket': 'tiquete inválido',
'Key bindings': 'Key bindings', 'Key bindings': 'Key bindings',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'language file "%(filename)s" created/updated': 'archivo de lenguaje "%(filename)s" creado/actualizado', 'language file "%(filename)s" created/updated': 'archivo de lenguaje "%(filename)s" creado/actualizado',
'Language files (static strings) updated': 'Archivos de lenguaje (cadenas estáticas) actualizados', 'Language files (static strings) updated': 'Archivos de lenguaje (cadenas estáticas) actualizados',
'languages': 'lenguajes', 'languages': 'lenguajes',
@@ -188,6 +214,7 @@
'Last saved on:': 'Guardado en:', 'Last saved on:': 'Guardado en:',
'License for': 'Licencia para', 'License for': 'Licencia para',
'loading...': 'cargando...', 'loading...': 'cargando...',
'locals': 'locals',
'Locals##debug': 'Locals', 'Locals##debug': 'Locals',
'Login': 'Inicio de sesión', 'Login': 'Inicio de sesión',
'login': 'inicio de sesión', 'login': 'inicio de sesión',
@@ -195,6 +222,7 @@
'Logout': 'fin de sesión', 'Logout': 'fin de sesión',
'Lost Password': 'Contraseña perdida', 'Lost Password': 'Contraseña perdida',
'manage': 'manage', 'manage': 'manage',
'Manage': 'Manage',
'merge': 'combinar', 'merge': 'combinar',
'Models': 'Modelos', 'Models': 'Modelos',
'models': 'modelos', 'models': 'modelos',
@@ -202,16 +230,22 @@
'modules': 'módulos', 'modules': 'módulos',
'Name': 'Nombre', 'Name': 'Nombre',
'new application "%s" created': 'nueva aplicación "%s" creada', 'new application "%s" created': 'nueva aplicación "%s" creada',
'New application wizard': 'New application wizard',
'new plugin installed': 'nuevo plugin instalado', 'new plugin installed': 'nuevo plugin instalado',
'New Record': 'Registro nuevo', 'New Record': 'Registro nuevo',
'new record inserted': 'nuevo registro insertado', 'new record inserted': 'nuevo registro insertado',
'New simple application': 'New simple application',
'next': 'next', 'next': 'next',
'next 100 rows': '100 filas siguientes', 'next 100 rows': '100 filas siguientes',
'NO': 'NO', 'NO': 'NO',
'No databases in this application': 'No hay bases de datos en esta aplicación', 'No databases in this application': 'No hay bases de datos en esta aplicación',
'No Interaction yet': 'No Interaction yet', 'No Interaction yet': 'No Interaction yet',
'no match': 'no encontrado', 'no match': 'no encontrado',
'no package selected': 'no package selected',
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'online designer': 'online designer',
'or alternatively': 'or alternatively', 'or alternatively': 'or alternatively',
'Or Get from URL:': 'Or Get from URL:',
'or import from csv file': 'o importar desde archivo CSV', 'or import from csv file': 'o importar desde archivo CSV',
'or provide app url:': 'o provea URL de la aplicación:', 'or provide app url:': 'o provea URL de la aplicación:',
'or provide application url:': 'o provea URL de la aplicación:', 'or provide application url:': 'o provea URL de la aplicación:',
@@ -220,6 +254,7 @@
'Overwrite installed app': 'sobreescriba aplicación instalada', 'Overwrite installed app': 'sobreescriba aplicación instalada',
'Pack all': 'empaquetar todo', 'Pack all': 'empaquetar todo',
'Pack compiled': 'empaquete compiladas', 'Pack compiled': 'empaquete compiladas',
'Pack custom': 'Pack custom',
'pack plugin': 'empaquetar plugin', 'pack plugin': 'empaquetar plugin',
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, no puede cambiar la contraseña aquí', 'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, no puede cambiar la contraseña aquí',
'Password': 'Contraseña', 'Password': 'Contraseña',
@@ -236,6 +271,7 @@
'Private files': 'Private files', 'Private files': 'Private files',
'private files': 'private files', 'private files': 'private files',
'Query:': 'Consulta:', 'Query:': 'Consulta:',
'Rapid Search': 'Rapid Search',
'record': 'registro', 'record': 'registro',
'record does not exist': 'el registro no existe', 'record does not exist': 'el registro no existe',
'record id': 'id de registro', 'record id': 'id de registro',
@@ -244,11 +280,14 @@
'Register': 'Registrese', 'Register': 'Registrese',
'Registration key': 'Contraseña de Registro', 'Registration key': 'Contraseña de Registro',
'reload': 'reload', 'reload': 'reload',
'Reload routes': 'Reload routes',
'Remove compiled': 'eliminar compiladas', 'Remove compiled': 'eliminar compiladas',
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s', 'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
'Replace': 'Replace', 'Replace': 'Replace',
'Replace All': 'Replace All', 'Replace All': 'Replace All',
'request': 'request',
'Resolve Conflict file': 'archivo Resolución de Conflicto', 'Resolve Conflict file': 'archivo Resolución de Conflicto',
'response': 'response',
'restore': 'restaurar', 'restore': 'restaurar',
'return': 'return', 'return': 'return',
'revert': 'revertir', 'revert': 'revertir',
@@ -256,26 +295,32 @@
'Rows in table': 'Filas en la tabla', 'Rows in table': 'Filas en la tabla',
'Rows selected': 'Filas seleccionadas', 'Rows selected': 'Filas seleccionadas',
'rules are not defined': 'rules are not defined', 'rules are not defined': 'rules are not defined',
'Run tests in this file': 'Run tests in this file',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "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')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'Running on %s',
'Save': 'Save', 'Save': 'Save',
'save': 'guardar', 'save': 'guardar',
'Save file:': 'Save file:', 'Save file:': 'Save file:',
'Save file: %s': 'Save file: %s',
'Save via Ajax': 'Save via Ajax', 'Save via Ajax': 'Save via Ajax',
'Saved file hash:': 'Hash del archivo guardado:', 'Saved file hash:': 'Hash del archivo guardado:',
'selected': 'seleccionado(s)', 'selected': 'seleccionado(s)',
'session': 'session',
'session expired': 'sesión expirada', 'session expired': 'sesión expirada',
'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s', 'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s',
'shell': 'shell', 'shell': 'shell',
'Site': 'sitio', 'Site': 'sitio',
'some files could not be removed': 'algunos archivos no pudieron ser removidos', 'some files could not be removed': 'algunos archivos no pudieron ser removidos',
'Start searching': 'Start searching', 'Start searching': 'Start searching',
'Start wizard': 'Start wizard',
'state': 'estado', 'state': 'estado',
'static': 'estáticos',
'Static': 'Static', 'Static': 'Static',
'static': 'estáticos',
'Static files': 'Archivos estáticos', 'Static files': 'Archivos estáticos',
'step': 'step', 'step': 'step',
'stop': 'stop', 'stop': 'stop',
'submit': 'enviar', 'submit': 'enviar',
'Submit': 'Submit',
'successful': 'successful', 'successful': 'successful',
'Sure you want to delete this object?': '¿Está seguro que desea eliminar este objeto?', 'Sure you want to delete this object?': '¿Está seguro que desea eliminar este objeto?',
'table': 'tabla', 'table': 'tabla',
@@ -303,6 +348,7 @@
'This is the %(filename)s template': 'Esta es la plantilla %(filename)s', 'This is the %(filename)s template': 'Esta es la plantilla %(filename)s',
'this page to see if a breakpoint was hit and debug interaction is required.': 'this page to see if a breakpoint was hit and debug interaction is required.', 'this page to see if a breakpoint was hit and debug interaction is required.': 'this page to see if a breakpoint was hit and debug interaction is required.',
'Ticket': 'Tiquete', 'Ticket': 'Tiquete',
'Ticket ID': 'Ticket ID',
'Timestamp': 'Timestamp', 'Timestamp': 'Timestamp',
'TM': 'MR', 'TM': 'MR',
'to previous version.': 'a la versión previa.', 'to previous version.': 'a la versión previa.',
@@ -311,10 +357,13 @@
'to use the debugger!': 'to use the debugger!', 'to use the debugger!': 'to use the debugger!',
'toggle breakpoint': 'toggle breakpoint', 'toggle breakpoint': 'toggle breakpoint',
'Toggle Fullscreen': 'Toggle Fullscreen', 'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'translation strings for the application': 'cadenas de caracteres de traducción para la aplicación', 'translation strings for the application': 'cadenas de caracteres de traducción para la aplicación',
'Translation strings for the application': 'Translation strings for the application', 'Translation strings for the application': 'Translation strings for the application',
'try': 'intente', 'try': 'intente',
'try something like': 'intente algo como', 'try something like': 'intente algo como',
'Try the mobile interface': 'Try the mobile interface',
'try view': 'try view',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.', 'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'No es posible verificar la existencia de actualizaciones', 'Unable to check for upgrades': 'No es posible verificar la existencia de actualizaciones',
'unable to create application "%s"': 'no es posible crear la aplicación "%s"', 'unable to create application "%s"': 'no es posible crear la aplicación "%s"',
@@ -332,9 +381,12 @@
'update': 'actualizar', 'update': 'actualizar',
'update all languages': 'actualizar todos los lenguajes', 'update all languages': 'actualizar todos los lenguajes',
'Update:': 'Actualice:', 'Update:': 'Actualice:',
'upgrade now to %s': 'upgrade now to %s',
'upgrade web2py now': 'actualize web2py ahora', 'upgrade web2py now': 'actualize web2py ahora',
'Upload': 'Upload', 'Upload': 'Upload',
'Upload & install packed application': 'Suba e instale aplicación empaquetada', 'Upload & install packed application': 'Suba e instale aplicación empaquetada',
'Upload a package:': 'Upload a package:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'subir aplicación:', 'upload application:': 'subir aplicación:',
'Upload existing application': 'Suba esta aplicación', 'Upload existing application': 'Suba esta aplicación',
'upload file:': 'suba archivo:', 'upload file:': 'suba archivo:',
@@ -348,6 +400,7 @@
'view': 'vista', 'view': 'vista',
'Views': 'Vistas', 'Views': 'Vistas',
'views': 'vistas', 'views': 'vistas',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py está actualizado', 'web2py is up to date': 'web2py está actualizado',
'web2py online debugger': 'web2py online debugger', 'web2py online debugger': 'web2py online debugger',
'web2py Recent Tweets': 'Tweets Recientes de web2py', 'web2py Recent Tweets': 'Tweets Recientes de web2py',
+1 -1
View File
@@ -49,7 +49,7 @@ def verify_password(password):
session.pam_user = None session.pam_user = None
if DEMO_MODE: if DEMO_MODE:
return True return True
elif not 'password' in _config: elif not _config.get('password'):
return False return False
elif _config['password'].startswith('pam_user:'): elif _config['password'].startswith('pam_user:'):
session.pam_user = _config['password'][9:].strip() session.pam_user = _config['password'][9:].strip()
+1 -1
View File
@@ -2,5 +2,5 @@
theme = web2py theme = web2py
[editor] [editor]
theme = night theme = web2py
@@ -4,7 +4,7 @@
position: absolute ! important; position: absolute ! important;
top: 0; left: 0; top: 0; left: 0;
width: 100% ! important; width: 100% ! important;
z-index: 9999; z-index: 1028;
} }
/* BREAKPOINTS */ /* BREAKPOINTS */
+20 -8
View File
@@ -277,6 +277,8 @@
ajax_page: function (method, action, data, target, element) { ajax_page: function (method, action, data, target, element) {
/* element is a new parameter, but should be put be put in front */ /* element is a new parameter, but should be put be put in front */
if(element == undefined) element = $(document); if(element == undefined) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
if(target == undefined || target == '') target = 'w2p_none';
if(web2py.fire(element, 'ajax:before', null, target )) { /*test a usecase, should stop here if returns false */ if(web2py.fire(element, 'ajax:before', null, target )) { /*test a usecase, should stop here if returns false */
$.ajax({ $.ajax({
'type': method, 'type': method,
@@ -303,12 +305,7 @@
}, },
'complete': function (xhr, status) { 'complete': function (xhr, status) {
web2py.fire(element, 'ajax:complete', [xhr, status], target); web2py.fire(element, 'ajax:complete', [xhr, status], target);
var html = xhr.responseText; web2py.updatePage(xhr, target); /* Parse and load the html received */
var content = xhr.getResponseHeader('web2py-component-content');
var t = $('#' + target);
if(content == 'prepend') t.prepend(html);
else if(content == 'append') t.append(html);
else if(content != 'hide') t.html(html);
web2py.trap_form(action, target); web2py.trap_form(action, target);
web2py.trap_link(target); web2py.trap_link(target);
web2py.ajax_init('#' + target); web2py.ajax_init('#' + target);
@@ -378,6 +375,20 @@
} }
}); });
}, },
updatePage: function (xhr, target) {
var t = $('#' + target);
var html = $.parseHTML(xhr.responseText, document, true);
var title_elements = $(html).filter('title').add($(html).find('title'));
var title = title_elements.last().text();
if (title) {
title_elements.remove(); /* Remove any title elements from the response */
document.title = $.trim(title); /* Set the new document title */
}
var content = xhr.getResponseHeader('web2py-component-content');
if(content == 'prepend') t.prepend(xhr.responseText);
else if(content == 'append') t.append(xhr.responseText);
else if(content != 'hide') t.html(html);
},
calc_entropy: function (mystring) { calc_entropy: function (mystring) {
/* calculate a simple entropy for a given string */ /* calculate a simple entropy for a given string */
var csets = new Array( var csets = new Array(
@@ -569,9 +580,9 @@
} }
if(target == undefined) { if(target == undefined) {
if(method == 'GET') { if(method == 'GET') {
web2py.ajax_page('get', action, [], 'bogus', el); //fixme? web2py.ajax_page('get', action, [], '', el);
} else if(method == 'POST') { } else if(method == 'POST') {
web2py.ajax_page('post', action, [], 'bogus', el); //fixme? web2py.ajax_page('post', action, [], '', el);
} }
} else { } else {
if(method == 'GET') { if(method == 'GET') {
@@ -689,3 +700,4 @@ web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy; web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/ */
/* compatibility code - end*/ /* compatibility code - end*/
@@ -30,6 +30,4 @@ jQuery(function(){
hoverMenu(); // first page load hoverMenu(); // first page load
jQuery(window).resize(hoverMenu); // on resize event jQuery(window).resize(hoverMenu); // on resize event
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');}); jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
}); });
@@ -79,8 +79,8 @@
return marker; return marker;
} }
{{if filetype=='html':}} {{if filetype in ('html', 'js', 'css'):}}
// must be here or break emmet/zencoding for html // must be here or break emmet/zencoding
CodeMirror.defaults.extraKeys["Ctrl-S"] = CodeMirror.defaults.extraKeys["Ctrl-S"] =
function(instance) { function(instance) {
doClickSave();}; doClickSave();};
+20 -8
View File
@@ -277,6 +277,8 @@
ajax_page: function (method, action, data, target, element) { ajax_page: function (method, action, data, target, element) {
/* element is a new parameter, but should be put be put in front */ /* element is a new parameter, but should be put be put in front */
if(element == undefined) element = $(document); if(element == undefined) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
if(target == undefined || target == '') target = 'w2p_none';
if(web2py.fire(element, 'ajax:before', null, target )) { /*test a usecase, should stop here if returns false */ if(web2py.fire(element, 'ajax:before', null, target )) { /*test a usecase, should stop here if returns false */
$.ajax({ $.ajax({
'type': method, 'type': method,
@@ -303,12 +305,7 @@
}, },
'complete': function (xhr, status) { 'complete': function (xhr, status) {
web2py.fire(element, 'ajax:complete', [xhr, status], target); web2py.fire(element, 'ajax:complete', [xhr, status], target);
var html = xhr.responseText; web2py.updatePage(xhr, target); /* Parse and load the html received */
var content = xhr.getResponseHeader('web2py-component-content');
var t = $('#' + target);
if(content == 'prepend') t.prepend(html);
else if(content == 'append') t.append(html);
else if(content != 'hide') t.html(html);
web2py.trap_form(action, target); web2py.trap_form(action, target);
web2py.trap_link(target); web2py.trap_link(target);
web2py.ajax_init('#' + target); web2py.ajax_init('#' + target);
@@ -378,6 +375,20 @@
} }
}); });
}, },
updatePage: function (xhr, target) {
var t = $('#' + target);
var html = $.parseHTML(xhr.responseText, document, true);
var title_elements = $(html).filter('title').add($(html).find('title'));
var title = title_elements.last().text();
if (title) {
title_elements.remove(); /* Remove any title elements from the response */
document.title = $.trim(title); /* Set the new document title */
}
var content = xhr.getResponseHeader('web2py-component-content');
if(content == 'prepend') t.prepend(xhr.responseText);
else if(content == 'append') t.append(xhr.responseText);
else if(content != 'hide') t.html(html);
},
calc_entropy: function (mystring) { calc_entropy: function (mystring) {
/* calculate a simple entropy for a given string */ /* calculate a simple entropy for a given string */
var csets = new Array( var csets = new Array(
@@ -569,9 +580,9 @@
} }
if(target == undefined) { if(target == undefined) {
if(method == 'GET') { if(method == 'GET') {
web2py.ajax_page('get', action, [], 'bogus', el); //fixme? web2py.ajax_page('get', action, [], '', el);
} else if(method == 'POST') { } else if(method == 'POST') {
web2py.ajax_page('post', action, [], 'bogus', el); //fixme? web2py.ajax_page('post', action, [], '', el);
} }
} else { } else {
if(method == 'GET') { if(method == 'GET') {
@@ -689,3 +700,4 @@ web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy; web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/ */
/* compatibility code - end*/ /* compatibility code - end*/
@@ -30,6 +30,4 @@ jQuery(function(){
hoverMenu(); // first page load hoverMenu(); // first page load
jQuery(window).resize(hoverMenu); // on resize event jQuery(window).resize(hoverMenu); // on resize event
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');}); jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
}); });
+20 -8
View File
@@ -277,6 +277,8 @@
ajax_page: function (method, action, data, target, element) { ajax_page: function (method, action, data, target, element) {
/* element is a new parameter, but should be put be put in front */ /* element is a new parameter, but should be put be put in front */
if(element == undefined) element = $(document); if(element == undefined) element = $(document);
/* if target is not there, fill it with something that there isn't in the page*/
if(target == undefined || target == '') target = 'w2p_none';
if(web2py.fire(element, 'ajax:before', null, target )) { /*test a usecase, should stop here if returns false */ if(web2py.fire(element, 'ajax:before', null, target )) { /*test a usecase, should stop here if returns false */
$.ajax({ $.ajax({
'type': method, 'type': method,
@@ -303,12 +305,7 @@
}, },
'complete': function (xhr, status) { 'complete': function (xhr, status) {
web2py.fire(element, 'ajax:complete', [xhr, status], target); web2py.fire(element, 'ajax:complete', [xhr, status], target);
var html = xhr.responseText; web2py.updatePage(xhr, target); /* Parse and load the html received */
var content = xhr.getResponseHeader('web2py-component-content');
var t = $('#' + target);
if(content == 'prepend') t.prepend(html);
else if(content == 'append') t.append(html);
else if(content != 'hide') t.html(html);
web2py.trap_form(action, target); web2py.trap_form(action, target);
web2py.trap_link(target); web2py.trap_link(target);
web2py.ajax_init('#' + target); web2py.ajax_init('#' + target);
@@ -378,6 +375,20 @@
} }
}); });
}, },
updatePage: function (xhr, target) {
var t = $('#' + target);
var html = $.parseHTML(xhr.responseText, document, true);
var title_elements = $(html).filter('title').add($(html).find('title'));
var title = title_elements.last().text();
if (title) {
title_elements.remove(); /* Remove any title elements from the response */
document.title = $.trim(title); /* Set the new document title */
}
var content = xhr.getResponseHeader('web2py-component-content');
if(content == 'prepend') t.prepend(xhr.responseText);
else if(content == 'append') t.append(xhr.responseText);
else if(content != 'hide') t.html(html);
},
calc_entropy: function (mystring) { calc_entropy: function (mystring) {
/* calculate a simple entropy for a given string */ /* calculate a simple entropy for a given string */
var csets = new Array( var csets = new Array(
@@ -569,9 +580,9 @@
} }
if(target == undefined) { if(target == undefined) {
if(method == 'GET') { if(method == 'GET') {
web2py.ajax_page('get', action, [], 'bogus', el); //fixme? web2py.ajax_page('get', action, [], '', el);
} else if(method == 'POST') { } else if(method == 'POST') {
web2py.ajax_page('post', action, [], 'bogus', el); //fixme? web2py.ajax_page('post', action, [], '', el);
} }
} else { } else {
if(method == 'GET') { if(method == 'GET') {
@@ -689,3 +700,4 @@ web2py_trap_link = jQuery.web2py.trap_link;
web2py_calc_entropy = jQuery.web2py.calc_entropy; web2py_calc_entropy = jQuery.web2py.calc_entropy;
*/ */
/* compatibility code - end*/ /* compatibility code - end*/
@@ -30,6 +30,4 @@ jQuery(function(){
hoverMenu(); // first page load hoverMenu(); // first page load
jQuery(window).resize(hoverMenu); // on resize event jQuery(window).resize(hoverMenu); // on resize event
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');}); jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
}); });
+16 -14
View File
@@ -5,7 +5,7 @@
Usage: Usage:
Install py2exe: http://sourceforge.net/projects/py2exe/files/ Install py2exe: http://sourceforge.net/projects/py2exe/files/
Copy script to the web2py directory Copy script to the web2py directory
c:\bin\python26\python build_windows_exe.py py2exe c:\bin\python26\python setup_exe.py py2exe
Adapted from http://bazaar.launchpad.net/~flavour/sahana-eden/trunk/view/head:/static/scripts/tools/standalone_exe.py Adapted from http://bazaar.launchpad.net/~flavour/sahana-eden/trunk/view/head:/static/scripts/tools/standalone_exe.py
""" """
@@ -42,18 +42,18 @@ remove_build_files = Config.getboolean("Setup", "remove_build_files")
# Python base version # Python base version
python_version = sys.version[:3] python_version = sys.version_info[:3]
# List of modules deprecated in python2.6 that are in the above set # List of modules deprecated in python2.6 that are in the above set
py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter'] py26_deprecated = ['mhlib', 'multifile', 'mimify', 'sets', 'MimeWriter']
if python_version == '2.6': if python_version > (2,5):
base_modules += ['json', 'multiprocessing'] base_modules += ['json', 'multiprocessing', 'ldap']
base_modules = list(set(base_modules).difference(set(py26_deprecated))) base_modules = list(set(base_modules).difference(set(py26_deprecated)))
#I don't know if this is even necessary #I don't know if this is even necessary
if python_version == '2.6': if python_version > (2,5):
# Python26 compatibility: http://www.py2exe.org/index.cgi/Tutorial#Step52 # Python26 compatibility: http://www.py2exe.org/index.cgi/Tutorial#Step52
try: try:
shutil.copytree('C:\Bin\Microsoft.VC90.CRT', 'dist/') shutil.copytree('C:\Bin\Microsoft.VC90.CRT', 'dist/')
@@ -62,8 +62,11 @@ if python_version == '2.6':
setup( setup(
console=['web2py.py'], console=[{'script':'web2py.py',
'icon_resources': [(0, 'extras/icons/web2py.ico')]
}],
windows=[{'script':'web2py.py', windows=[{'script':'web2py.py',
'icon_resources': [(1, 'extras/icons/web2py.ico')],
'dest_base':'web2py_no_console' # MUST NOT be just 'web2py' otherwise it overrides the standard web2py.exe 'dest_base':'web2py_no_console' # MUST NOT be just 'web2py' otherwise it overrides the standard web2py.exe
}], }],
name="web2py", name="web2py",
@@ -72,14 +75,9 @@ setup(
author="Massimo DiPierro", author="Massimo DiPierro",
license="LGPL v3", license="LGPL v3",
data_files=[ data_files=[
'ABOUT', 'ABOUT',
'LICENSE', 'LICENSE',
'VERSION', 'VERSION'
'splashlogo.gif',
'logging.example.conf',
'options_std.py',
'app.example.yaml',
'queue.example.yaml'
], ],
options={'py2exe': { options={'py2exe': {
'packages': contributed_modules, 'packages': contributed_modules,
@@ -126,6 +124,10 @@ else:
copy_folders('applications/examples', 'applications/examples') copy_folders('applications/examples', 'applications/examples')
print "Only web2py's admin, examples & welcome applications have been added" print "Only web2py's admin, examples & welcome applications have been added"
copy_folders('extras', 'extras')
copy_folders('examples', 'examples')
copy_folders('handlers', 'handlers')
#should we copy project's site-packages into dist/site-packages #should we copy project's site-packages into dist/site-packages
if copy_site_packages: if copy_site_packages:
+1 -1
View File
@@ -10,7 +10,7 @@ Web2Py framework modules
======================== ========================
""" """
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64'] __all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
from globals import current from globals import current
from html import * from html import *
+28 -12
View File
@@ -310,6 +310,11 @@ if not 'google' in DRIVERS:
except ImportError: except ImportError:
LOGGER.debug('no MySQL driver MySQLDB') LOGGER.debug('no MySQL driver MySQLDB')
try:
import mysql.connector as mysqlconnector
DRIVERS.append("MySQL(mysqlconnector)")
except ImportError:
LOGGER.debug("no driver mysql.connector")
try: try:
import psycopg2 import psycopg2
@@ -2150,9 +2155,15 @@ class BaseAdapter(ConnectionPool):
for row in rowsobj.records: for row in rowsobj.records:
box = row[tablename] box = row[tablename]
for f,v in fields_virtual: for f,v in fields_virtual:
box[f] = v.f(row) try:
box[f] = v.f(row)
except AttributeError:
pass # not enough fields to define virtual field
for f,v in fields_lazy: for f,v in fields_lazy:
box[f] = (v.handler or VirtualCommand)(v.f,row) try:
box[f] = (v.handler or VirtualCommand)(v.f,row)
except AttributeError:
pass # not enough fields to define virtual field
### old style virtual fields ### old style virtual fields
for item in table.virtualfields: for item in table.virtualfields:
@@ -2439,7 +2450,7 @@ class JDBCSQLiteAdapter(SQLiteAdapter):
class MySQLAdapter(BaseAdapter): class MySQLAdapter(BaseAdapter):
drivers = ('MySQLdb','pymysql') drivers = ('MySQLdb','pymysql', 'mysqlconnector')
commit_on_alter_table = True commit_on_alter_table = True
support_distributed_transaction = True support_distributed_transaction = True
@@ -6498,9 +6509,10 @@ class IMAPAdapter(NoSQLAdapter):
def _insert(self, table, fields): def _insert(self, table, fields):
def add_payload(message, obj): def add_payload(message, obj):
payload = Message() payload = Message()
charset = obj.get("encoding", "utf-8") payload.set_charset(obj.get("encoding", "utf-8"))
payload.set_type(obj.get("mime", None)) mime = obj.get("mime", None)
payload.set_charset(charset) if mime:
payload.set_type(mime)
if "text" in obj: if "text" in obj:
payload.set_payload(obj["text"]) payload.set_payload(obj["text"])
elif "payload" in obj: elif "payload" in obj:
@@ -6538,7 +6550,9 @@ class IMAPAdapter(NoSQLAdapter):
else: else:
message[item] = ";".join([i for i in message[item] = ";".join([i for i in
value]) value])
if not message.is_multipart(): if (not message.is_multipart() and
(not message.get_content_type().startswith(
"multipart"))):
if isinstance(content, basestring): if isinstance(content, basestring):
message.set_payload(content) message.set_payload(content)
elif len(content) > 0: elif len(content) > 0:
@@ -8443,8 +8457,10 @@ class Table(object):
clones.append(field.clone( clones.append(field.clone(
unique=False, type=field.type if nfk else 'bigint')) unique=False, type=field.type if nfk else 'bigint'))
archive_db.define_table( archive_db.define_table(
archive_name, Field(current_record,field_type, archive_name,
label=current_record_label), *clones) Field(current_record,field_type,label=current_record_label),
*clones,**dict(format=self._format))
self._before_update.append( self._before_update.append(
lambda qset,fs,db=archive_db,an=archive_name,cn=current_record: lambda qset,fs,db=archive_db,an=archive_name,cn=current_record:
archive_record(qset,fs,db[an],cn)) archive_record(qset,fs,db[an],cn))
@@ -9203,13 +9219,13 @@ class Expression(object):
def startswith(self, value): def startswith(self, value):
db = self.db db = self.db
if not self.type in ('string', 'text', 'json'): if not self.type in ('string', 'text', 'json', 'upload'):
raise SyntaxError("startswith used with incompatible field type") raise SyntaxError("startswith used with incompatible field type")
return Query(db, db._adapter.STARTSWITH, self, value) return Query(db, db._adapter.STARTSWITH, self, value)
def endswith(self, value): def endswith(self, value):
db = self.db db = self.db
if not self.type in ('string', 'text', 'json'): if not self.type in ('string', 'text', 'json', 'upload'):
raise SyntaxError("endswith used with incompatible field type") raise SyntaxError("endswith used with incompatible field type")
return Query(db, db._adapter.ENDSWITH, self, value) return Query(db, db._adapter.ENDSWITH, self, value)
@@ -9227,7 +9243,7 @@ class Expression(object):
return self.contains('') return self.contains('')
else: else:
return reduce(all and AND or OR,subqueries) return reduce(all and AND or OR,subqueries)
if not self.type in ('string', 'text', 'json') and not self.type.startswith('list:'): if not self.type in ('string', 'text', 'json', 'upload') and not self.type.startswith('list:'):
raise SyntaxError("contains used with incompatible field type") raise SyntaxError("contains used with incompatible field type")
return Query(db, db._adapter.CONTAINS, self, value, case_sensitive=case_sensitive) return Query(db, db._adapter.CONTAINS, self, value, case_sensitive=case_sensitive)
+81 -39
View File
@@ -211,12 +211,12 @@ class Request(Storage):
body.seek(0) body.seek(0)
# parse POST variables on POST, PUT, BOTH only in post_vars # parse POST variables on POST, PUT, BOTH only in post_vars
if (body and if (body and not is_json
env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH') and and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
not is_json): query_string = env.pop('QUERY_STRING',None)
query_string = env.pop('QUERY_STRING') if 'QUERY_STRING' in env else None
dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1) dpost = cgi.FieldStorage(fp=body, environ=env, keep_blank_values=1)
post_vars.update(dpost) if len(dpost):
post_vars.update(dpost)
if query_string is not None: if query_string is not None:
env['QUERY_STRING'] = query_string env['QUERY_STRING'] = query_string
# The same detection used by FieldStorage to detect multipart POSTs # The same detection used by FieldStorage to detect multipart POSTs
@@ -232,19 +232,13 @@ class Request(Storage):
if key is None: if key is None:
continue # not sure why cgi.FieldStorage returns None key continue # not sure why cgi.FieldStorage returns None key
dpk = dpost[key] dpk = dpost[key]
# if an element is not a file replace it with its value else leave it alone # if an element is not a file replace it with
if isinstance(dpk, list): # its value else leave it alone
value = []
for _dpk in dpk: pvalue = listify([(_dpk if _dpk.filename else _dpk.value)
if not _dpk.filename: for _dpk in dpk]
value.append(_dpk.value) if isinstance(dpk, list) else
else: (dpk if dpk.filename else dpk.value))
value.append(_dpk)
elif not dpk.filename:
value = dpk.value
else:
value = dpk
pvalue = listify(value)
if len(pvalue): if len(pvalue):
post_vars[key] = (len(pvalue) > 1 and pvalue) or pvalue[0] post_vars[key] = (len(pvalue) > 1 and pvalue) or pvalue[0]
@@ -333,13 +327,12 @@ class Request(Storage):
_self.args[-1], _, self.extension = self.args[-1].rpartition('.') _self.args[-1], _, self.extension = self.args[-1].rpartition('.')
current.response.headers['Content-Type'] = \ current.response.headers['Content-Type'] = \
contenttype('.' + _self.extension.lower()) contenttype('.' + _self.extension.lower())
if not method in ['GET', 'POST', 'DELETE', 'PUT']:
raise HTTP(400, "invalid method")
rest_action = _action().get(method, None) rest_action = _action().get(method, None)
if not rest_action: if not (rest_action and method==method.upper()
and callable(rest_action)):
raise HTTP(400, "method not supported") raise HTTP(400, "method not supported")
try: try:
return rest_action(*_self.args, **_self.vars) return rest_action(*_self.args, **getattr(_self,'vars',{}))
except TypeError, e: except TypeError, e:
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
if len(traceback.extract_tb(exc_traceback)) == 1: if len(traceback.extract_tb(exc_traceback)) == 1:
@@ -742,10 +735,11 @@ class Session(Storage):
response.session_cookie_compression_level = compression_level response.session_cookie_compression_level = compression_level
# check if there is a session_id in cookies # check if there is a session_id in cookies
try: try:
response.session_id = cookies[response.session_id_name].value old_session_id = cookies[response.session_id_name].value
except KeyError: except KeyError:
response.session_id = None old_session_id = None
response.session_id = old_session_id
# if we are supposed to use cookie based session data # if we are supposed to use cookie based session data
if cookie_key: if cookie_key:
@@ -865,12 +859,30 @@ class Session(Storage):
else: else:
response.session_id = None response.session_id = None
response.session_new = True response.session_new = True
# if there is no session id yet, we'll need to create a
# new session
else:
response.session_new = True
# set the cookie now if you know the session_id so user can set
# cookie attributes in controllers/models
# cookie will be reset later
# yet cookie may be reset later
# Removed comparison between old and new session ids - should send
# the cookie all the time
if isinstance(response.session_id,str):
response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = '/'
if cookie_expires:
response.cookies[response.session_id_name]['expires'] = \
cookie_expires.strftime(FMT)
session_pickled = cPickle.dumps(self)
response.session_hash = hashlib.md5(session_pickled).hexdigest()
if self.flash: if self.flash:
(response.flash, self.flash) = (self.flash, None) (response.flash, self.flash) = (self.flash, None)
session_pickled = cPickle.dumps(self)
response.session_hash = hashlib.md5(session_pickled).hexdigest()
def renew(self, clear_session=False): def renew(self, clear_session=False):
@@ -929,6 +941,30 @@ class Session(Storage):
else: else:
response.session_new = True response.session_new = True
def _fixup_before_save(self):
response = current.response
rcookies = response.cookies
if self._forget and response.session_id_name in rcookies:
del rcookies[response.session_id_name]
elif self._secure and response.session_id_name in rcookies:
rcookies[response.session_id_name]['secure'] = True
def clear_session_cookies(sefl):
request = current.request
response = current.response
session = response.session
masterapp = response.session_masterapp
cookies = request.cookies
rcookies = response.cookies
# if not cookie_key, but session_data_name in cookies
# expire session_data_name from cookies
if response.session_data_name in cookies:
rcookies[response.session_data_name] = 'expired'
rcookies[response.session_data_name]['path'] = '/'
rcookies[response.session_data_name]['expires'] = PAST
if response.session_id_name in rcookies:
del rcookies[response.session_id_name]
def save_session_id_cookie(self): def save_session_id_cookie(self):
request = current.request request = current.request
response = current.response response = current.response
@@ -946,12 +982,11 @@ class Session(Storage):
if response.session_id: if response.session_id:
rcookies[response.session_id_name] = response.session_id rcookies[response.session_id_name] = response.session_id
rcookies[response.session_id_name]['path'] = '/' rcookies[response.session_id_name]['path'] = '/'
if isinstance(response.session_cookie_expires,datetime.datetime): expires = response.session_cookie_expires
rcookies[response.session_id_name]['expires'] = \ if isinstance(expires,datetime.datetime):
response.session_cookie_expires.strftime(FMT) expires = expires.strftime(FMT)
elif isinstance(response.session_cookie_expires,str): if expires:
rcookies[response.session_id_name]['expires'] = \ rcookies[response.session_id_name]['expires'] = expires
response.session_cookie_expires
def clear(self): def clear(self):
Storage.clear(self) Storage.clear(self)
@@ -981,6 +1016,7 @@ class Session(Storage):
def _try_store_in_cookie(self, request, response): def _try_store_in_cookie(self, request, response):
if self._forget or self._unchanged(response): if self._forget or self._unchanged(response):
# self.clear_session_cookies()
self.save_session_id_cookie() self.save_session_id_cookie()
return False return False
name = response.session_data_name name = response.session_data_name
@@ -988,13 +1024,15 @@ class Session(Storage):
value = secure_dumps(dict(self), value = secure_dumps(dict(self),
response.session_cookie_key, response.session_cookie_key,
compression_level=compression_level) compression_level=compression_level)
expires = response.session_cookie_expires
rcookies = response.cookies rcookies = response.cookies
rcookies.pop(name, None) rcookies.pop(name, None)
rcookies[name] = value rcookies[name] = value
rcookies[name]['path'] = '/' rcookies[name]['path'] = '/'
expires = response.session_cookie_expires
if isinstance(expires,datetime.datetime):
expires = expires.strftime(FMT)
if expires: if expires:
rcookies[name]['expires'] = expires.strftime(FMT) rcookies[name]['expires'] = expires
return True return True
def _unchanged(self,response): def _unchanged(self,response):
@@ -1006,13 +1044,15 @@ class Session(Storage):
def _try_store_in_db(self, request, response): def _try_store_in_db(self, request, response):
# don't save if file-based sessions, # don't save if file-based sessions,
# no session id, or session being forgotten # no session id, or session being forgotten
# or no changes to session # or no changes to session (Unless the session is new)
if (not response.session_db_table or
if not response.session_db_table or self._forget or self._unchanged(response): self._forget or
(self._unchanged(response) and not response.session_new)):
if (not response.session_db_table and if (not response.session_db_table and
global_settings.db_sessions is not True and global_settings.db_sessions is not True and
response.session_masterapp in global_settings.db_sessions): response.session_masterapp in global_settings.db_sessions):
global_settings.db_sessions.remove(response.session_masterapp) global_settings.db_sessions.remove(response.session_masterapp)
# self.clear_session_cookies()
self.save_session_id_cookie() self.save_session_id_cookie()
return False return False
@@ -1050,7 +1090,9 @@ class Session(Storage):
def _try_store_in_file(self, request, response): def _try_store_in_file(self, request, response):
try: try:
if not response.session_id or self._forget or self._unchanged(response): if (not response.session_id or self._forget
or self._unchanged(response)):
# self.clear_session_cookies()
self.save_session_id_cookie() self.save_session_id_cookie()
return False return False
if response.session_new or not response.session_file: if response.session_new or not response.session_file:
+2 -2
View File
@@ -67,8 +67,8 @@ regex_param = re.compile(r'{(?P<s>.+?)}')
# pattern for a valid accept_language # pattern for a valid accept_language
regex_language = \ regex_language = \
re.compile('([a-z]{2}(?:\-[a-z]{2})?(?:\-[a-z]{2})?)(?:[,;]|$)') re.compile('([a-z]{2,3}(?:\-[a-z]{2})?(?:\-[a-z]{2})?)(?:[,;]|$)')
regex_langfile = re.compile('^[a-z]{2}(-[a-z]{2})?\.py$') regex_langfile = re.compile('^[a-z]{2,3}(-[a-z]{2})?\.py$')
regex_backslash = re.compile(r"\\([\\{}%])") regex_backslash = re.compile(r"\\([\\{}%])")
regex_plural = re.compile('%({.+?})') regex_plural = re.compile('%({.+?})')
regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)} regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$') # %%{word(varname or number)}
+8 -10
View File
@@ -503,12 +503,8 @@ def wsgibase(environ, responder):
# store cookies in headers # store cookies in headers
# ################################################## # ##################################################
rcookies = response.cookies session._fixup_before_save()
if session._forget and response.session_id_name in rcookies: http_response.cookies2headers(response.cookies)
del rcookies[response.session_id_name]
elif session._secure:
rcookies[response.session_id_name]['secure'] = True
http_response.cookies2headers(rcookies)
ticket = None ticket = None
@@ -612,7 +608,8 @@ def save_password(password, port):
def appfactory(wsgiapp=wsgibase, def appfactory(wsgiapp=wsgibase,
logfilename='httpserver.log', logfilename='httpserver.log',
profiler_dir=None): profiler_dir=None,
profilerfilename=None):
""" """
generates a wsgi application that does logging and profiling and calls generates a wsgi application that does logging and profiling and calls
wsgibase wsgibase
@@ -623,7 +620,8 @@ def appfactory(wsgiapp=wsgibase,
[, profilerfilename='profiler.log']]]) [, profilerfilename='profiler.log']]])
""" """
if profilerfilename is not None:
raise BaseException("Deprecated API")
if profiler_dir: if profiler_dir:
profiler_dir = abspath(profiler_dir) profiler_dir = abspath(profiler_dir)
logger.warn('profiler is on. will use dir %s', profiler_dir) logger.warn('profiler is on. will use dir %s', profiler_dir)
@@ -631,14 +629,14 @@ def appfactory(wsgiapp=wsgibase,
try: try:
os.makedirs(profiler_dir) os.makedirs(profiler_dir)
except: except:
raise BaseException, "Can't create dir %s" % profiler_dir raise BaseException("Can't create dir %s" % profiler_dir)
filepath = pjoin(profiler_dir, 'wtest') filepath = pjoin(profiler_dir, 'wtest')
try: try:
filehandle = open( filepath, 'w' ) filehandle = open( filepath, 'w' )
filehandle.close() filehandle.close()
os.unlink(filepath) os.unlink(filepath)
except IOError: except IOError:
raise BaseException, "Unable to write to dir %s" % profiler_dir raise BaseException("Unable to write to dir %s" % profiler_dir)
def app_with_logging(environ, responder): def app_with_logging(environ, responder):
""" """
+1 -2
View File
@@ -292,6 +292,7 @@ class ListWidget(StringWidget):
_class = 'string' _class = 'string'
requires = field.requires if isinstance( requires = field.requires if isinstance(
field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None
if isinstance(value,str): value = [value]
nvalue = value or [''] nvalue = value or ['']
items = [LI(INPUT(_id=_id, _class=_class, _name=_name, items = [LI(INPUT(_id=_id, _class=_class, _name=_name,
value=v, hideerror=k < len(nvalue) - 1, value=v, hideerror=k < len(nvalue) - 1,
@@ -1356,8 +1357,6 @@ class SQLFORM(FORM):
value = self.record[fieldname] value = self.record[fieldname]
else: else:
value = self.table[fieldname].default value = self.table[fieldname].default
if field.type.startswith('list:') and isinstance(value, str):
value = [value]
row_id = '%s_%s%s' % ( row_id = '%s_%s%s' % (
self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) self.table, fieldname, SQLFORM.ID_ROW_SUFFIX)
widget = field.widget(field, value) widget = field.widget(field, value)
+1 -1
View File
@@ -1509,7 +1509,7 @@ class Auth(object):
self.bar = SPAN(s1, Anr(items[0]['name'], self.bar = SPAN(s1, Anr(items[0]['name'],
_href=items[0]['href']), s3, _href=items[0]['href']), s3,
_class='auth_navbar') _class='auth_navbar')
for item in items: for item in items[1:]:
self.bar.insert(-1, s2) self.bar.insert(-1, s2)
self.bar.insert(-1, Anr(item['name'], _href=item['href'])) self.bar.insert(-1, Anr(item['name'], _href=item['href']))
+38 -1
View File
@@ -42,6 +42,7 @@ __all__ = [
'IS_DATETIME', 'IS_DATETIME',
'IS_DECIMAL_IN_RANGE', 'IS_DECIMAL_IN_RANGE',
'IS_EMAIL', 'IS_EMAIL',
'IS_LIST_OF_EMAILS',
'IS_EMPTY_OR', 'IS_EMPTY_OR',
'IS_EXPR', 'IS_EXPR',
'IS_FLOAT_IN_RANGE', 'IS_FLOAT_IN_RANGE',
@@ -1178,6 +1179,39 @@ class IS_EMAIL(Validator):
return (value, None) return (value, None)
return (value, translate(self.error_message)) return (value, translate(self.error_message))
class IS_LIST_OF_EMAILS(object):
"""
use as follows:
Field('emails','list:string',
widget=SQLFORM.widgets.text.widget,
requires=IS_LIST_OF_EMAILS(),
represent=lambda v,r: \
SPAN(*[A(x,_href='mailto:'+x) for x in (v or [])])
)
"""
split_emails = re.compile('[^,;\s]+')
def __init__(self, error_message = 'Invalid emails: %s'):
self.error_message = error_message
def __call__(self, value):
bad_emails = []
emails = []
f = IS_EMAIL()
for email in self.split_emails.findall(value):
if not email in emails:
emails.append(email)
error = f(email)[1]
if error and not email in bad_emails:
bad_emails.append(email)
if not bad_emails:
return (value, None)
else:
return (value,
translate(self.error_message) % ', '.join(bad_emails))
def formatter(self,value,row=None):
return ', '.join(value or [])
# URL scheme source: # URL scheme source:
# <http://en.wikipedia.org/wiki/URI_scheme> obtained on 2008-Nov-10 # <http://en.wikipedia.org/wiki/URI_scheme> obtained on 2008-Nov-10
@@ -2890,7 +2924,8 @@ class CRYPT(object):
key=None, key=None,
digest_alg='pbkdf2(1000,20,sha512)', digest_alg='pbkdf2(1000,20,sha512)',
min_length=0, min_length=0,
error_message='too short', salt=True): error_message='too short', salt=True,
max_length=1024):
""" """
important, digest_alg='md5' is not the default hashing algorithm for important, digest_alg='md5' is not the default hashing algorithm for
web2py. This is only an example of usage of this function. web2py. This is only an example of usage of this function.
@@ -2901,10 +2936,12 @@ class CRYPT(object):
self.key = key self.key = key
self.digest_alg = digest_alg self.digest_alg = digest_alg
self.min_length = min_length self.min_length = min_length
self.max_length = max_length
self.error_message = error_message self.error_message = error_message
self.salt = salt self.salt = salt
def __call__(self, value): def __call__(self, value):
value = value and value[:self.max_length]
if len(value) < self.min_length: if len(value) < self.min_length:
return ('', translate(self.error_message)) return ('', translate(self.error_message))
return (LazyCrypt(self, value), None) return (LazyCrypt(self, value), None)