Compare commits

..
47 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
mdipierro b0f6dc4e16 R-2.6.2 2013-09-13 17:44:01 -05:00
mdipierro 1499b19575 R-2.6.2 2013-09-13 17:41:13 -05:00
mdipierro 877b867cef Merge pull request #212 from niphlod/fix/js_punctuation
never got to understand ASI. Let's behave and have a semicolon.
2013-09-13 15:27:46 -07:00
niphlod 837808bd77 never got to understand ASI. Let's behave and have a semicolon. 2013-09-14 00:22:53 +02:00
mdipierro 10a69f4338 removed circular import for validators 2013-09-13 17:14:47 -05:00
mdipierro 7bc603f380 fixed Pointless to set session.flash before session.renew, thanks nursix 2013-09-13 15:41:58 -05:00
mdipierro 6c1cc6fc96 tabs -> spaces 2013-09-13 15:39:39 -05:00
47 changed files with 443 additions and 264 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.1-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.1-stable+timestamp.2013.09.12.17.08.08 Version 2.6.4-stable+timestamp.2013.09.21.20.41.28
+13 -13
View File
@@ -50,7 +50,7 @@ if request.function == 'manage':
auth.requires_membership(manager_role)(lambda: None)() auth.requires_membership(manager_role)(lambda: None)()
menu = False menu = False
elif (request.application == 'admin' and not session.authorized) or \ elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index', redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars)))) vars=dict(send=URL(args=request.args, vars=request.vars))))
else: else:
@@ -540,30 +540,30 @@ def table_template(table):
def bg_graph_model(): def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR') graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict() subgraphs = dict()
for tablename in db.tables: for tablename in db.tables:
if hasattr(db[tablename],'_meta_graphmodel'): if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel meta_graphmodel = db[tablename]._meta_graphmodel
else: else:
meta_graphmodel = dict(group='Undefined', color='#ECECEC') meta_graphmodel = dict(group='Undefined', color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '') group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group): if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
else: else:
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext', graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename)) label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()): for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'], graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n, name='cluster%d' % n,
style='filled', style='filled',
color=subgraphs[key]['meta']['color'], color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group']) label=subgraphs[key]['meta']['group'])
for tablename in db.tables: for tablename in db.tables:
for field in db[tablename]: for field in db[tablename]:
@@ -580,14 +580,14 @@ def bg_graph_model():
#return graph.draw(format='png', prog='dot') #return graph.draw(format='png', prog='dot')
if not request.args: if not request.args:
return graph.draw(format='png', prog='dot') return graph.draw(format='png', prog='dot')
else: else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0) response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot': if request.args(0) == 'dot':
return graph.string() return graph.string()
else: else:
return graph.draw(format=request.args(0), prog='dot') return graph.draw(format=request.args(0), prog='dot')
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) return dict(databases=databases, pgv=pgv)
def manage(): def manage():
+1 -2
View File
@@ -212,7 +212,7 @@ def toggle_breakpoint():
except Exception, e: except Exception, e:
session.flash = str(e) session.flash = str(e)
return response.json({'ok': ok, 'lineno': lineno}) return response.json({'ok': ok, 'lineno': lineno})
def list_breakpoints(): def list_breakpoints():
"Return a list of linenumbers for current breakpoints" "Return a list of linenumbers for current breakpoints"
@@ -234,4 +234,3 @@ def list_breakpoints():
session.flash = str(e) session.flash = str(e)
ok = False ok = False
return response.json({'ok': ok, 'breakpoints': breakpoints}) return response.json({'ok': ok, 'breakpoints': breakpoints})
+4 -4
View File
@@ -88,7 +88,7 @@ def safe_write(a, value, b='w'):
def get_app(name=None): def get_app(name=None):
app = name or request.args(0) app = name or request.args(0)
if (app and os.path.exists(apath(app, r=request)) and if (app and os.path.exists(apath(app, r=request)) and
(not MULTI_USER_MODE or is_manager() or (not MULTI_USER_MODE or is_manager() or
db(db.app.name == app)(db.app.owner == auth.user.id).count())): db(db.app.name == app)(db.app.owner == auth.user.id).count())):
return app return app
@@ -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)
@@ -583,11 +583,11 @@ def edit():
# show settings tab and save prefernces # show settings tab and save prefernces
if 'settings' in request.vars: if 'settings' in request.vars:
if request.post_vars: #save new preferences if request.post_vars: #save new preferences
if config.save(request.post_vars.items()): if config.save(request.post_vars.items()):
response.headers["web2py-component-flash"] = T('Preferences saved correctly') response.headers["web2py-component-flash"] = T('Preferences saved correctly')
else: else:
response.headers["web2py-component-flash"] = T('Preferences saved on session only') response.headers["web2py-component-flash"] = T('Preferences saved on session only')
response.headers["web2py-component-command"] = "update_theme('%s'); jQuery('a[href=#editor_settings] button.close').click();" % config.read()['theme'] response.headers["web2py-component-command"] = "update_theme('%s'); jQuery('a[href=#editor_settings] button.close').click();" % config.read()['theme']
return return
else: else:
+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()
+6
View File
@@ -0,0 +1,6 @@
[DEFAULT]
theme = web2py
[editor]
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 */
+22 -10
View File
@@ -179,7 +179,7 @@
}); });
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
doc.on('click', "input.datetime", function () { doc.on('click', "input.datetime", function () {
var tformat = $(this).data('w2p_datetime_format') var tformat = $(this).data('w2p_datetime_format');
var active = $(this).data('w2p_datetime'); var active = $(this).data('w2p_datetime');
var format = (typeof tformat != 'undefined') ? tformat : datetime_format; var format = (typeof tformat != 'undefined') ? tformat : datetime_format;
if(active === undefined) { if(active === undefined) {
@@ -196,7 +196,7 @@
}); });
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
doc.on('click', "input.date", function () { doc.on('click', "input.date", function () {
var tformat = $(this).data('w2p_date_format') var tformat = $(this).data('w2p_date_format');
var active = $(this).data('w2p_date'); var active = $(this).data('w2p_date');
var format = (typeof tformat != 'undefined') ? tformat : date_format; var format = (typeof tformat != 'undefined') ? tformat : date_format;
if(active === undefined) { if(active === undefined) {
@@ -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();};
+13 -13
View File
@@ -50,7 +50,7 @@ if request.function == 'manage':
auth.requires_membership(manager_role)(lambda: None)() auth.requires_membership(manager_role)(lambda: None)()
menu = False menu = False
elif (request.application == 'admin' and not session.authorized) or \ elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index', redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars)))) vars=dict(send=URL(args=request.args, vars=request.vars))))
else: else:
@@ -540,30 +540,30 @@ def table_template(table):
def bg_graph_model(): def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR') graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict() subgraphs = dict()
for tablename in db.tables: for tablename in db.tables:
if hasattr(db[tablename],'_meta_graphmodel'): if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel meta_graphmodel = db[tablename]._meta_graphmodel
else: else:
meta_graphmodel = dict(group='Undefined', color='#ECECEC') meta_graphmodel = dict(group='Undefined', color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '') group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group): if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
else: else:
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext', graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename)) label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()): for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'], graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n, name='cluster%d' % n,
style='filled', style='filled',
color=subgraphs[key]['meta']['color'], color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group']) label=subgraphs[key]['meta']['group'])
for tablename in db.tables: for tablename in db.tables:
for field in db[tablename]: for field in db[tablename]:
@@ -580,14 +580,14 @@ def bg_graph_model():
#return graph.draw(format='png', prog='dot') #return graph.draw(format='png', prog='dot')
if not request.args: if not request.args:
return graph.draw(format='png', prog='dot') return graph.draw(format='png', prog='dot')
else: else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0) response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot': if request.args(0) == 'dot':
return graph.string() return graph.string()
else: else:
return graph.draw(format=request.args(0), prog='dot') return graph.draw(format=request.args(0), prog='dot')
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) return dict(databases=databases, pgv=pgv)
def manage(): def manage():
+4 -4
View File
@@ -5,12 +5,12 @@ def get(args):
return None return None
try: try:
obj = globals(),get(args[0]) obj = globals(),get(args[0])
for k in range(1,len(args)): for k in range(1,len(args)):
obj = getattr(obj,args[k]) obj = getattr(obj,args[k])
return obj return obj
except: except:
return None return None
def vars(): def vars():
"""the running controller function!""" """the running controller function!"""
title = '.'.join(request.args) title = '.'.join(request.args)
@@ -27,7 +27,7 @@ def vars():
d = getattr(obj,'__bases__',None) d = getattr(obj,'__bases__',None)
for key in keys: for key in keys:
a = getattr(obj,key,None) a = getattr(obj,key,None)
if a and not isinstance(a,DAL): if a and not isinstance(a,DAL):
doc1 = getattr(a, '__doc__', '') doc1 = getattr(a, '__doc__', '')
t1 = type(a) t1 = type(a)
@@ -38,7 +38,7 @@ def vars():
else: else:
doc = 'Unkown' doc = 'Unkown'
keys = [] keys = []
t = c = d = None t = c = d = None
else: else:
raise HTTP(400) raise HTTP(400)
return dict( return dict(
@@ -1,4 +1,3 @@
def hello1(): def hello1():
""" simple page without template """ """ simple page without template """
+22 -10
View File
@@ -179,7 +179,7 @@
}); });
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
doc.on('click', "input.datetime", function () { doc.on('click', "input.datetime", function () {
var tformat = $(this).data('w2p_datetime_format') var tformat = $(this).data('w2p_datetime_format');
var active = $(this).data('w2p_datetime'); var active = $(this).data('w2p_datetime');
var format = (typeof tformat != 'undefined') ? tformat : datetime_format; var format = (typeof tformat != 'undefined') ? tformat : datetime_format;
if(active === undefined) { if(active === undefined) {
@@ -196,7 +196,7 @@
}); });
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
doc.on('click', "input.date", function () { doc.on('click', "input.date", function () {
var tformat = $(this).data('w2p_date_format') var tformat = $(this).data('w2p_date_format');
var active = $(this).data('w2p_date'); var active = $(this).data('w2p_date');
var format = (typeof tformat != 'undefined') ? tformat : date_format; var format = (typeof tformat != 'undefined') ? tformat : date_format;
if(active === undefined) { if(active === undefined) {
@@ -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');
}); });
+13 -13
View File
@@ -50,7 +50,7 @@ if request.function == 'manage':
auth.requires_membership(manager_role)(lambda: None)() auth.requires_membership(manager_role)(lambda: None)()
menu = False menu = False
elif (request.application == 'admin' and not session.authorized) or \ elif (request.application == 'admin' and not session.authorized) or \
(request.application != 'admin' and not gluon.fileutils.check_credentials(request)): (request.application != 'admin' and not gluon.fileutils.check_credentials(request)):
redirect(URL('admin', 'default', 'index', redirect(URL('admin', 'default', 'index',
vars=dict(send=URL(args=request.args, vars=request.vars)))) vars=dict(send=URL(args=request.args, vars=request.vars))))
else: else:
@@ -540,30 +540,30 @@ def table_template(table):
def bg_graph_model(): def bg_graph_model():
graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR') graph = pgv.AGraph(layout='dot', directed=True, strict=False, rankdir='LR')
subgraphs = dict() subgraphs = dict()
for tablename in db.tables: for tablename in db.tables:
if hasattr(db[tablename],'_meta_graphmodel'): if hasattr(db[tablename],'_meta_graphmodel'):
meta_graphmodel = db[tablename]._meta_graphmodel meta_graphmodel = db[tablename]._meta_graphmodel
else: else:
meta_graphmodel = dict(group='Undefined', color='#ECECEC') meta_graphmodel = dict(group='Undefined', color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '') group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group): if not subgraphs.has_key(group):
subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
else: else:
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
graph.add_node(tablename, name=tablename, shape='plaintext', graph.add_node(tablename, name=tablename, shape='plaintext',
label=table_template(tablename)) label=table_template(tablename))
for n, key in enumerate(subgraphs.iterkeys()): for n, key in enumerate(subgraphs.iterkeys()):
graph.subgraph(nbunch=subgraphs[key]['tables'], graph.subgraph(nbunch=subgraphs[key]['tables'],
name='cluster%d' % n, name='cluster%d' % n,
style='filled', style='filled',
color=subgraphs[key]['meta']['color'], color=subgraphs[key]['meta']['color'],
label=subgraphs[key]['meta']['group']) label=subgraphs[key]['meta']['group'])
for tablename in db.tables: for tablename in db.tables:
for field in db[tablename]: for field in db[tablename]:
@@ -580,14 +580,14 @@ def bg_graph_model():
#return graph.draw(format='png', prog='dot') #return graph.draw(format='png', prog='dot')
if not request.args: if not request.args:
return graph.draw(format='png', prog='dot') return graph.draw(format='png', prog='dot')
else: else:
response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0) response.headers['Content-Disposition']='attachment;filename=graph.%s'%request.args(0)
if request.args(0) == 'dot': if request.args(0) == 'dot':
return graph.string() return graph.string()
else: else:
return graph.draw(format=request.args(0), prog='dot') return graph.draw(format=request.args(0), prog='dot')
def graph_model(): def graph_model():
return dict(databases=databases, pgv=pgv) return dict(databases=databases, pgv=pgv)
def manage(): def manage():
+1 -1
View File
@@ -31,7 +31,7 @@ def user():
http://..../[app]/default/user/profile http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in http://..../[app]/default/user/manage_users (requires membership in
use @auth.requires_login() use @auth.requires_login()
@auth.requires_membership('group name') @auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id) @auth.requires_permission('read','table name',record_id)
+22 -10
View File
@@ -179,7 +179,7 @@
}); });
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S"; var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
doc.on('click', "input.datetime", function () { doc.on('click', "input.datetime", function () {
var tformat = $(this).data('w2p_datetime_format') var tformat = $(this).data('w2p_datetime_format');
var active = $(this).data('w2p_datetime'); var active = $(this).data('w2p_datetime');
var format = (typeof tformat != 'undefined') ? tformat : datetime_format; var format = (typeof tformat != 'undefined') ? tformat : datetime_format;
if(active === undefined) { if(active === undefined) {
@@ -196,7 +196,7 @@
}); });
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d"; var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
doc.on('click', "input.date", function () { doc.on('click', "input.date", function () {
var tformat = $(this).data('w2p_date_format') var tformat = $(this).data('w2p_date_format');
var active = $(this).data('w2p_date'); var active = $(this).data('w2p_date');
var format = (typeof tformat != 'undefined') ? tformat : date_format; var format = (typeof tformat != 'undefined') ? tformat : date_format;
if(active === undefined) { if(active === undefined) {
@@ -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 *
+1 -1
View File
@@ -47,7 +47,7 @@ class MemcacheClient(object):
value = obj[1] + value value = obj[1] + value
self.client.set(key, (time.time(), value)) self.client.set(key, (time.time(), value))
return value return value
def incr(self, key, value=1): def incr(self, key, value=1):
return self.increment(key, value) return self.increment(key, value)
+4 -4
View File
@@ -9,7 +9,7 @@
Note: This module is intended as a plugin replacement of pbkdf2.py Note: This module is intended as a plugin replacement of pbkdf2.py
by Armin Ronacher. by Armin Ronacher.
Git repository: Git repository:
$ git clone https://github.com/michele-comitini/pbkdf2_ctypes.git $ git clone https://github.com/michele-comitini/pbkdf2_ctypes.git
:copyright: Copyright (c) 2013: Michele Comitini <mcm@glisco.it> :copyright: Copyright (c) 2013: Michele Comitini <mcm@glisco.it>
@@ -79,7 +79,7 @@ def openssl_hashlib_to_crypto_map_get(hashfunc):
crypto_hashfunc.restype = ctypes.c_void_p crypto_hashfunc.restype = ctypes.c_void_p
return crypto_hashfunc() return crypto_hashfunc()
def openssl_pbkdf2(data, salt, iterations, digest, keylen): def openssl_pbkdf2(data, salt, iterations, digest, keylen):
"""OpenSSL compatibile wrapper """OpenSSL compatibile wrapper
""" """
@@ -92,7 +92,7 @@ def openssl_pbkdf2(data, salt, iterations, digest, keylen):
c_iter = ctypes.c_int(iterations) c_iter = ctypes.c_int(iterations)
c_keylen = ctypes.c_int(keylen) c_keylen = ctypes.c_int(keylen)
c_buff = ctypes.create_string_buffer(keylen) c_buff = ctypes.create_string_buffer(keylen)
# PKCS5_PBKDF2_HMAC(const char *pass, int passlen, # PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
# const unsigned char *salt, int saltlen, int iter, # const unsigned char *salt, int saltlen, int iter,
# const EVP_MD *digest, # const EVP_MD *digest,
@@ -102,7 +102,7 @@ def openssl_pbkdf2(data, salt, iterations, digest, keylen):
ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int,
ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p,
ctypes.c_int, ctypes.c_char_p] ctypes.c_int, ctypes.c_char_p]
crypto.PKCS5_PBKDF2_HMAC.restype = ctypes.c_int crypto.PKCS5_PBKDF2_HMAC.restype = ctypes.c_int
err = crypto.PKCS5_PBKDF2_HMAC(c_pass, c_passlen, err = crypto.PKCS5_PBKDF2_HMAC(c_pass, c_passlen,
c_salt, c_saltlen, c_salt, c_saltlen,
+7 -7
View File
@@ -46,7 +46,7 @@ class PDF(object):
canvas.setLineWidth(4) canvas.setLineWidth(4)
canvas.line(0, -1.25 * cm, 21.7 * cm, -1.25 * cm) canvas.line(0, -1.25 * cm, 21.7 * cm, -1.25 * cm)
canvas.restoreState() canvas.restoreState()
canvas.saveState() canvas.saveState()
notes = listify(invoice.get('notes','')) notes = listify(invoice.get('notes',''))
textobject = canvas.beginText(1 * cm, -25 * cm) textobject = canvas.beginText(1 * cm, -25 * cm)
@@ -57,7 +57,7 @@ class PDF(object):
textobject.textLine('Pag.%s/%s' % (page+1,pages)) textobject.textLine('Pag.%s/%s' % (page+1,pages))
canvas.drawText(textobject) canvas.drawText(textobject)
canvas.restoreState() canvas.restoreState()
canvas.saveState() canvas.saveState()
business_details = listify(invoice.get('from','FROM:')) business_details = listify(invoice.get('from','FROM:'))
canvas.setFont(self.font_face, 9) canvas.setFont(self.font_face, 9)
@@ -66,13 +66,13 @@ class PDF(object):
textobject.textLine(line) textobject.textLine(line)
canvas.drawText(textobject) canvas.drawText(textobject)
canvas.restoreState() canvas.restoreState()
canvas.saveState() canvas.saveState()
client_info = listify(invoice.get('to','TO:')) client_info = listify(invoice.get('to','TO:'))
textobject = canvas.beginText(1.5 * cm, -2.5 * cm) textobject = canvas.beginText(1.5 * cm, -2.5 * cm)
for line in client_info: for line in client_info:
textobject.textLine(line) textobject.textLine(line)
canvas.drawText(textobject) canvas.drawText(textobject)
canvas.restoreState() canvas.restoreState()
textobject = canvas.beginText(1.5 * cm, -6.75 * cm) textobject = canvas.beginText(1.5 * cm, -6.75 * cm)
@@ -86,7 +86,7 @@ class PDF(object):
data = [invoice_items[0]] data = [invoice_items[0]]
for item in items: for item in items:
data.append([ data.append([
self.format_currency(x) self.format_currency(x)
if isinstance(x,float) else x if isinstance(x,float) else x
for x in item]) for x in item])
righta = [k for k,v in enumerate(items[0]) righta = [k for k,v in enumerate(items[0])
@@ -116,10 +116,10 @@ class PDF(object):
data = [invoice['totals'][0]] data = [invoice['totals'][0]]
for item in items: for item in items:
data.append([ data.append([
self.format_currency(x) self.format_currency(x)
if isinstance(x,float) else x if isinstance(x,float) else x
for x in item]) for x in item])
righta = [k for k,v in enumerate(items[0]) righta = [k for k,v in enumerate(items[0])
if isinstance(v,(int,float,Decimal))] if isinstance(v,(int,float,Decimal))]
total = self.format_currency(invoice['total']) total = self.format_currency(invoice['total'])
data.append(['']*(len(items[0])-1)+[total]) data.append(['']*(len(items[0])-1)+[total])
+5 -5
View File
@@ -90,13 +90,13 @@ def populate(table, n=None, default=True, compute=False, contents={}):
can be used in two ways: can be used in two ways:
>>> populate(db.tablename, n=100) >>> populate(db.tablename, n=100)
or or
>>> for k,row in enumerate(populate(db.tablename)): print row >>> for k,row in enumerate(populate(db.tablename)): print row
""" """
generator = populate_generator(table, default=default, generator = populate_generator(table, default=default,
compute=compute, contents=contents) compute=compute, contents=contents)
if n is not None: if n is not None:
for k,record in enumerate(generator): for k,record in enumerate(generator):
@@ -246,8 +246,8 @@ def populate_generator(table, default=True, compute=False, contents={}):
record[fieldname] = random.choice(LAST_NAMES) record[fieldname] = random.choice(LAST_NAMES)
elif fieldname.find('username')>=0: elif fieldname.find('username')>=0:
record[fieldname] = random.choice(FIRST_NAMES).lower()+str(random.randint(1000,9999)) record[fieldname] = random.choice(FIRST_NAMES).lower()+str(random.randint(1000,9999))
else: else:
record[fieldname] = random.choice(FIRST_NAMES)+' '+random.choice(LAST_NAMES) record[fieldname] = random.choice(FIRST_NAMES)+' '+random.choice(LAST_NAMES)
elif fieldname.find('phone')>=0: elif fieldname.find('phone')>=0:
record[fieldname] = '(%s%s%s) %s%s%s-%s%s%s%s' % ( record[fieldname] = '(%s%s%s) %s%s%s-%s%s%s%s' % (
random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890')) random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'),random.choice('1234567890'))
+1 -1
View File
@@ -244,7 +244,7 @@ def run(history, statement, env={}):
for name, val in statement_module.__dict__.items(): for name, val in statement_module.__dict__.items():
if name not in old_globals or represent(val) != old_globals[name]: if name not in old_globals or represent(val) != old_globals[name]:
new_globals[name] = val new_globals[name] = val
if True in [isinstance(val, tuple(UNPICKLABLE_TYPES)) if True in [isinstance(val, tuple(UNPICKLABLE_TYPES))
for val in new_globals.values()]: for val in new_globals.values()]:
# this statement added an unpicklable global. store the statement and # this statement added an unpicklable global. store the statement and
+1 -1
View File
@@ -65,7 +65,7 @@ class Stripe:
def refund(self, charge_id): def refund(self, charge_id):
params = urllib.urlencode({}) params = urllib.urlencode({})
u = urllib.urlopen(self.URL_REFUND % (self.key, charge_id), u = urllib.urlopen(self.URL_REFUND % (self.key, charge_id),
params) params)
return simplejson.loads(u.read()) return simplejson.loads(u.read())
+1 -1
View File
@@ -123,7 +123,7 @@ class WebClient(object):
else: else:
self.method = 'GET' if method=='auto' else method self.method = 'GET' if method=='auto' else method
data = None data = None
t0 = time.time() t0 = time.time()
self.response = opener.open(self.url, data) self.response = opener.open(self.url, data)
self.time = time.time() - t0 self.time = time.time() - t0
except urllib2.HTTPError, error: except urllib2.HTTPError, error:
+54 -42
View File
@@ -234,12 +234,6 @@ except ImportError:
except ImportError: except ImportError:
simplejson = None simplejson = None
try:
from gluon import validators
have_validators = True
except (ImportError, SyntaxError):
have_validators = False
LOGGER = logging.getLogger("web2py.dal") LOGGER = logging.getLogger("web2py.dal")
DEFAULT = lambda:0 DEFAULT = lambda:0
@@ -316,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
@@ -1085,7 +1084,7 @@ class BaseAdapter(ConnectionPool):
elif not key in sql_fields: elif not key in sql_fields:
del sql_fields_current[key] del sql_fields_current[key]
ftype = sql_fields_old[key]['type'] ftype = sql_fields_old[key]['type']
if (self.dbengine in ('postgres',) and if (self.dbengine in ('postgres',) and
ftype.startswith('geometry')): ftype.startswith('geometry')):
geotype, parms = ftype[:-1].split('(') geotype, parms = ftype[:-1].split('(')
schema = parms.split(',')[0] schema = parms.split(',')[0]
@@ -1095,7 +1094,7 @@ class BaseAdapter(ConnectionPool):
elif self.dbengine in ('firebird',): elif self.dbengine in ('firebird',):
query = ['ALTER TABLE %s DROP %s;' % (tablename, key)] query = ['ALTER TABLE %s DROP %s;' % (tablename, key)]
else: else:
query = ['ALTER TABLE %s DROP COLUMN %s;' % query = ['ALTER TABLE %s DROP COLUMN %s;' %
(tablename, key)] (tablename, key)]
metadata_change = True metadata_change = True
elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \ elif sql_fields[key]['sql'] != sql_fields_old[key]['sql'] \
@@ -1135,9 +1134,9 @@ class BaseAdapter(ConnectionPool):
self.log('faked!\n', table) self.log('faked!\n', table)
else: else:
self.execute(sub_query) self.execute(sub_query)
# Caveat: mysql, oracle and firebird # Caveat: mysql, oracle and firebird
# do not allow multiple alter table # do not allow multiple alter table
# in one transaction so we must commit # in one transaction so we must commit
# partial transactions and # partial transactions and
# update table._dbt after alter table. # update table._dbt after alter table.
if db._adapter.commit_on_alter_table: if db._adapter.commit_on_alter_table:
@@ -1790,7 +1789,7 @@ class BaseAdapter(ConnectionPool):
return self.connection.rollback() return self.connection.rollback()
def close_connection(self): def close_connection(self):
if self.connection: if self.connection:
r = self.connection.close() r = self.connection.close()
self.connection = None self.connection = None
return r return r
@@ -2156,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:
@@ -2445,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
@@ -4418,7 +4423,7 @@ class GoogleSQLAdapter(UseDatabaseStoredFile,MySQLAdapter):
def find_driver(self,adapter_args,uri=None): def find_driver(self,adapter_args,uri=None):
self.adapter_args = adapter_args self.adapter_args = adapter_args
self.driver = "google" self.driver = "google"
class NoSQLAdapter(BaseAdapter): class NoSQLAdapter(BaseAdapter):
can_select_for_update = False can_select_for_update = False
@@ -5372,7 +5377,7 @@ class MongoDBAdapter(NoSQLAdapter):
d = datetime.date(2000, 1, 1) d = datetime.date(2000, 1, 1)
# mongodb doesn't has a time object and so it must datetime, # mongodb doesn't has a time object and so it must datetime,
# string or integer # string or integer
return datetime.datetime.combine(d, value) return datetime.datetime.combine(d, value)
elif fieldtype == "blob": elif fieldtype == "blob":
from bson import Binary from bson import Binary
if not isinstance(value, Binary): if not isinstance(value, Binary):
@@ -6504,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:
@@ -6544,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:
@@ -6872,7 +6880,9 @@ def sqlhtml_validators(field):
fieldtype fieldtype
""" """
db = field.db db = field.db
if not have_validators: try:
from gluon import validators
except ImportError:
return [] return []
field_type, field_length = field.type, field.length field_type, field_length = field.type, field.length
if isinstance(field_type, SQLCustomType): if isinstance(field_type, SQLCustomType):
@@ -6938,14 +6948,14 @@ def sqlhtml_validators(field):
refs = reduce(lambda a,b:a&b, [count(ids[i:i+30]) for i in rx]) refs = reduce(lambda a,b:a&b, [count(ids[i:i+30]) for i in rx])
else: else:
refs = db(id.belongs(ids)).select(id) refs = db(id.belongs(ids)).select(id)
return (refs and ', '.join(f(r,x.id) for x in refs) or '') return (refs and ', '.join(f(r,x.id) for x in refs) or '')
field.represent = field.represent or list_ref_repr field.represent = field.represent or list_ref_repr
if hasattr(referenced, '_format') and referenced._format: if hasattr(referenced, '_format') and referenced._format:
requires = validators.IS_IN_DB(db,referenced._id, requires = validators.IS_IN_DB(db,referenced._id,
referenced._format,multiple=True) referenced._format,multiple=True)
else: else:
requires = validators.IS_IN_DB(db,referenced._id, requires = validators.IS_IN_DB(db,referenced._id,
multiple=True) multiple=True)
if field.unique: if field.unique:
requires._and = validators.IS_NOT_IN_DB(db,field) requires._and = validators.IS_NOT_IN_DB(db,field)
if not field.notnull: if not field.notnull:
@@ -8349,10 +8359,10 @@ class Table(object):
self._id = field self._id = field
for field in fields: for field in fields:
if isinstance(field, (FieldMethod, FieldVirtual)): if isinstance(field, (FieldMethod, FieldVirtual)):
virtual_fields.append(field) virtual_fields.append(field)
elif isinstance(field, Field) and not field.name in fieldnames: elif isinstance(field, Field) and not field.name in fieldnames:
if field.db is not None: if field.db is not None:
field = copy.copy(field) field = copy.copy(field)
include_new(field) include_new(field)
elif isinstance(field, dict) and not field['fieldname'] in fieldnames: elif isinstance(field, dict) and not field['fieldname'] in fieldnames:
include_new(Field(**field)) include_new(Field(**field))
@@ -8447,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))
@@ -8914,7 +8926,7 @@ class Table(object):
except ValueError: except ValueError:
raise RuntimeError("Unable to parse line:%s field:%s value:'%s'" raise RuntimeError("Unable to parse line:%s field:%s value:'%s'"
% (lineno+1,field,line[i])) % (lineno+1,field,line[i]))
if not (id_map or cid is None or id_offset is None or unique_idx): if not (id_map or cid is None or id_offset is None or unique_idx):
csv_id = long(line[cid]) csv_id = long(line[cid])
curr_id = self.insert(**dict(items)) curr_id = self.insert(**dict(items))
@@ -9207,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)
@@ -9231,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)
@@ -9613,7 +9625,7 @@ class Field(Expression):
else: else:
filename = name filename = name
# ## if file is in DB # ## if file is in DB
if isinstance(self_uploadfield, (str, Field)): if isinstance(self_uploadfield, (str, Field)):
return dict(path=None,filename=filename) return dict(path=None,filename=filename)
# ## if file is on filesystem # ## if file is on filesystem
if not path: if not path:
@@ -10422,24 +10434,24 @@ class Rows(object):
one_result = False one_result = False
if 'one_result' in args: if 'one_result' in args:
one_result = args['one_result'] one_result = args['one_result']
def build_fields_struct(row, fields, num, groups): def build_fields_struct(row, fields, num, groups):
''' helper function: ''' helper function:
''' '''
if num > len(fields)-1: if num > len(fields)-1:
if one_result: if one_result:
return row return row
else: else:
return [row] return [row]
key = fields[num] key = fields[num]
value = row[key] value = row[key]
if value not in groups: if value not in groups:
groups[value] = build_fields_struct(row, fields, num+1, {}) groups[value] = build_fields_struct(row, fields, num+1, {})
else: else:
struct = build_fields_struct(row, fields, num+1, groups[ value ]) struct = build_fields_struct(row, fields, num+1, groups[ value ])
# still have more grouping to do # still have more grouping to do
if type(struct) == type(dict()): if type(struct) == type(dict()):
groups[value].update() groups[value].update()
@@ -10449,22 +10461,22 @@ class Rows(object):
# no more grouping, first only on # no more grouping, first only on
else: else:
groups[value] = struct groups[value] = struct
return groups return groups
if len(fields) == 0: if len(fields) == 0:
return self return self
# if select returned no results # if select returned no results
if not self.records: if not self.records:
return {} return {}
grouped_row_group = dict() grouped_row_group = dict()
# build the struct # build the struct
for row in self: for row in self:
build_fields_struct(row, fields, 0, grouped_row_group) build_fields_struct(row, fields, 0, grouped_row_group)
return grouped_row_group return grouped_row_group
def render(self, i=None, fields=None): def render(self, i=None, fields=None):
+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:
+1 -1
View File
@@ -1484,7 +1484,7 @@ class A(DIV):
if not self['_disable_with']: if not self['_disable_with']:
self['_data-w2p_disable_with'] = 'default' self['_data-w2p_disable_with'] = 'default'
if self['callback'] and not self['_id']: if self['callback'] and not self['_id']:
self['_id'] = web2py_uuid() self['_id'] = web2py_uuid()
if self['delete']: if self['delete']:
self['_data-w2p_remove'] = self['delete'] self['_data-w2p_remove'] = self['delete']
if self['target']: if self['target']:
+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)}
+12 -14
View File
@@ -453,7 +453,7 @@ def wsgibase(environ, responder):
serve_controller(request, response, session) serve_controller(request, response, session)
except HTTP, http_response: except HTTP, http_response:
if static_file: if static_file:
return http_response.to(responder, env=env) return http_response.to(responder, env=env)
@@ -470,7 +470,7 @@ def wsgibase(environ, responder):
# ################################################## # ##################################################
# on success, commit database # on success, commit database
# ################################################## # ##################################################
if response.do_not_commit is True: if response.do_not_commit is True:
BaseAdapter.close_all_instances(None) BaseAdapter.close_all_instances(None)
elif response.custom_commit: elif response.custom_commit:
@@ -489,7 +489,7 @@ def wsgibase(environ, responder):
if request.cid: if request.cid:
http_response.headers.setdefault( http_response.headers.setdefault(
'web2py-component-content', 'replace') 'web2py-component-content', 'replace')
if request.ajax: if request.ajax:
if response.flash: if response.flash:
http_response.headers['web2py-component-flash'] = \ http_response.headers['web2py-component-flash'] = \
@@ -498,17 +498,13 @@ def wsgibase(environ, responder):
if response.js: if response.js:
http_response.headers['web2py-component-command'] = \ http_response.headers['web2py-component-command'] = \
urllib2.quote(response.js.replace('\n','')) urllib2.quote(response.js.replace('\n',''))
# ################################################## # ##################################################
# 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 -1
View File
@@ -48,7 +48,7 @@ class TicketStorage(Storage):
self._store_on_disk(request, ticket_id, ticket_data) self._store_on_disk(request, ticket_id, ticket_data)
def _store_in_db(self, request, ticket_id, ticket_data): def _store_in_db(self, request, ticket_id, ticket_data):
self.db._adapter.reconnect() self.db._adapter.reconnect()
try: try:
table = self._get_table(self.db, self.tablename, request.application) table = self._get_table(self.db, self.tablename, request.application)
id = table.insert(ticket_id=ticket_id, id = table.insert(ticket_id=ticket_id,
+6 -6
View File
@@ -244,15 +244,15 @@ def try_rewrite_on_error(http_response, request, environ, ticket=None):
url = path_info + '?' + query_string url = path_info + '?' + query_string
message = 'You are being redirected <a href="%s">here</a>' message = 'You are being redirected <a href="%s">here</a>'
return HTTP(303, message % url, Location=url), environ return HTTP(303, message % url, Location=url), environ
elif not environ.get('__ROUTES_ONERROR__', False): elif not environ.get('__ROUTES_ONERROR__', False):
# wsgibase will be called recursively with # wsgibase will be called recursively with
# the routes_onerror path. # the routes_onerror path.
environ['__ROUTES_ONERROR__'] = True # limit recursion environ['__ROUTES_ONERROR__'] = True # limit recursion
path_info = '/' + path_info.lstrip('/') path_info = '/' + path_info.lstrip('/')
environ['PATH_INFO'] = path_info environ['PATH_INFO'] = path_info
environ['QUERY_STRING'] = query_string environ['QUERY_STRING'] = query_string
environ['WEB2PY_STATUS_CODE'] = status environ['WEB2PY_STATUS_CODE'] = status
return None, environ return None, environ
# do nothing! # do nothing!
return http_response, environ return http_response, environ
+4 -4
View File
@@ -137,12 +137,12 @@ def env(
for k, v in extra_request.items(): for k, v in extra_request.items():
request[k] = v request[k] = v
path_info = '/%s/%s/%s' % (a, c, f) path_info = '/%s/%s/%s' % (a, c, f)
if request.args: if request.args:
path_info = '%s/%s' % (path_info, '/'.join(request.args)) path_info = '%s/%s' % (path_info, '/'.join(request.args))
if request.vars: if request.vars:
vars = ['%s=%s' % (k,v) if v else '%s' % k vars = ['%s=%s' % (k,v) if v else '%s' % k
for (k,v) in request.vars.iteritems()] for (k,v) in request.vars.iteritems()]
path_info = '%s?%s' % (path_info, '&'.join(vars)) path_info = '%s?%s' % (path_info, '&'.join(vars))
request.env.path_info = path_info request.env.path_info = path_info
@@ -238,7 +238,7 @@ def run(
pyfile = os.path.join('applications', a, 'controllers', c + '.py') pyfile = os.path.join('applications', a, 'controllers', c + '.py')
pycfile = os.path.join('applications', a, 'compiled', pycfile = os.path.join('applications', a, 'compiled',
"controllers_%s_%s.pyc" % (c, f)) "controllers_%s_%s.pyc" % (c, f))
if ((cronjob and os.path.isfile(pycfile)) if ((cronjob and os.path.isfile(pycfile))
or not os.path.isfile(pyfile)): or not os.path.isfile(pyfile)):
exec read_pyc(pycfile) in _env exec read_pyc(pycfile) in _env
elif os.path.isfile(pyfile): elif os.path.isfile(pyfile):
@@ -425,7 +425,7 @@ def execute_from_command_line(argv=None):
parser = optparse.OptionParser(usage=get_usage()) parser = optparse.OptionParser(usage=get_usage())
parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME', parser.add_option('-S', '--shell', dest='shell', metavar='APPNAME',
help='run web2py in interactive shell ' + help='run web2py in interactive shell ' +
'or IPython(if installed) with specified appname') 'or IPython(if installed) with specified appname')
msg = 'run web2py in interactive shell or bpython (if installed) with' msg = 'run web2py in interactive shell or bpython (if installed) with'
msg += ' specified appname (if app does not exist it will be created).' msg += ' specified appname (if app does not exist it will be created).'
+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
@@ -434,7 +434,7 @@ class TemplateParser(object):
# Get the filename; filename looks like ``"template.html"``. # Get the filename; filename looks like ``"template.html"``.
# We need to eval to remove the quotes and get the string type. # We need to eval to remove the quotes and get the string type.
filename = eval(filename, context) filename = eval(filename, context)
# Allow empty filename for conditional extend and include directives. # Allow empty filename for conditional extend and include directives.
if not filename: if not filename:
return '' return ''
+1 -1
View File
@@ -114,7 +114,7 @@ class TestRoutes(unittest.TestCase):
except AttributeError: except AttributeError:
pass pass
""" """
# outgoing # outgoing
self.assertEqual(filter_url('http://domain.com/init/default/index', self.assertEqual(filter_url('http://domain.com/init/default/index',
out=True), '/init/default/index') out=True), '/init/default/index')
self.assertEqual(filter_url('http://domain.com/init/default/index/arg1', out=True), '/init/default/index/arg1') self.assertEqual(filter_url('http://domain.com/init/default/index/arg1', out=True), '/init/default/index/arg1')
+1 -1
View File
@@ -120,7 +120,7 @@ class TestWeb(LiveTest):
# check registration and login were successful # check registration and login were successful
client.get('index') client.get('index')
# COMMENTED BECAUSE FAILS BUT WHY? # COMMENTED BECAUSE FAILS BUT WHY?
self.assertTrue('Welcome Homer' in client.text) self.assertTrue('Welcome Homer' in client.text)
+14 -14
View File
@@ -921,7 +921,7 @@ class Auth(object):
username_case_sensitive=True, username_case_sensitive=True,
update_fields = ['email'], update_fields = ['email'],
ondelete="CASCADE", ondelete="CASCADE",
client_side = True, client_side = True,
renew_session_onlogin=True, renew_session_onlogin=True,
renew_session_onlogout=True, renew_session_onlogout=True,
keep_session_onlogin=True, keep_session_onlogin=True,
@@ -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']))
@@ -1560,8 +1560,8 @@ class Auth(object):
current_record.replace('_',' ').title()) current_record.replace('_',' ').title())
for table in tables: for table in tables:
fieldnames = table.fields() fieldnames = table.fields()
if ('id' in fieldnames and if ('id' in fieldnames and
'modified_on' in fieldnames and 'modified_on' in fieldnames and
not current_record in fieldnames): not current_record in fieldnames):
table._enable_record_versioning( table._enable_record_versioning(
archive_db=archive_db, archive_db=archive_db,
@@ -2167,17 +2167,17 @@ class Auth(object):
elif 'username' in table_user.fields: elif 'username' in table_user.fields:
username = 'username' username = 'username'
else: else:
username = 'email' username = 'email'
settings = self.settings settings = self.settings
if 'username' in table_user.fields or \ if 'username' in table_user.fields or \
not settings.login_email_validate: not settings.login_email_validate:
tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty) tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
if not settings.username_case_sensitive: if not settings.username_case_sensitive:
tmpvalidator = [IS_LOWER(), tmpvalidator] tmpvalidator = [IS_LOWER(), tmpvalidator]
else: else:
tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email) tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
if not settings.email_case_sensitive: if not settings.email_case_sensitive:
tmpvalidator = [IS_LOWER(), tmpvalidator] tmpvalidator = [IS_LOWER(), tmpvalidator]
old_requires = table_user[username].requires old_requires = table_user[username].requires
table_user[username].requires = tmpvalidator table_user[username].requires = tmpvalidator
@@ -2191,7 +2191,7 @@ class Auth(object):
except: except:
pass pass
### use session for federated login ### use session for federated login
snext = self.get_vars_next() snext = self.get_vars_next()
if snext: if snext:
session._auth_next = snext session._auth_next = snext
@@ -2414,9 +2414,9 @@ class Auth(object):
next = cas.logout_url(next) next = cas.logout_url(next)
current.session.auth = None current.session.auth = None
current.session.flash = self.messages.logged_out
if self.settings.renew_session_onlogout: if self.settings.renew_session_onlogout:
current.session.renew(clear_session=not self.settings.keep_session_onlogout) current.session.renew(clear_session=not self.settings.keep_session_onlogout)
current.session.flash = self.messages.logged_out
if not next is None: if not next is None:
redirect(next) redirect(next)
@@ -2515,9 +2515,9 @@ class Auth(object):
captcha.comment, self.settings.formstyle, 'captcha__row') captcha.comment, self.settings.formstyle, 'captcha__row')
table_user.registration_key.default = key = web2py_uuid() table_user.registration_key.default = key = web2py_uuid()
if form.accepts(request, session if self.csrf_prevention else None, if form.accepts(request, session if self.csrf_prevention else None,
formname='register', formname='register',
onvalidation=onvalidation, onvalidation=onvalidation,
hideerror=self.settings.hideerror): hideerror=self.settings.hideerror):
description = self.messages.group_description % form.vars description = self.messages.group_description % form.vars
if self.settings.create_user_groups: if self.settings.create_user_groups:
@@ -5858,7 +5858,7 @@ class Wiki(object):
class Config(object): class Config(object):
def __init__( def __init__(
self, self,
filename, filename,
section, section,
default_values={} default_values={}
): ):
@@ -5879,11 +5879,11 @@ class Config(object):
def save(self, options): def save(self, options):
for option, value in options: for option, value in options:
self.config.set(self.section, option, value) self.config.set(self.section, option, value)
try: try:
self.config.write(open(self.filename, 'w')) self.config.write(open(self.filename, 'w'))
result = True result = True
except: except:
current.session['settings_%s' % self.section] = dict(self.config.items(self.section)) current.session['settings_%s' % self.section] = dict(self.config.items(self.section))
result = False result = False
return result return result
+2 -2
View File
@@ -310,7 +310,7 @@ def is_loopback_ip_address(ip=None, addrinfo=None):
if not isinstance(ip, basestring): if not isinstance(ip, basestring):
return False return False
# IPv4 or IPv6-embedded IPv4 or IPv4-compatible IPv6 # IPv4 or IPv6-embedded IPv4 or IPv4-compatible IPv6
if ip.count('.') == 3: if ip.count('.') == 3:
return ip.lower().startswith(('127', '::127', '0:0:0:0:0:0:127', return ip.lower().startswith(('127', '::127', '0:0:0:0:0:0:127',
'::ffff:127', '0:0:0:0:0:ffff:127')) '::ffff:127', '0:0:0:0:0:ffff:127'))
return ip == '::1' or ip == '0:0:0:0:0:0:0:1' # IPv6 loopback return ip == '::1' or ip == '0:0:0:0:0:0:0:1' # IPv6 loopback
@@ -322,7 +322,7 @@ def getipaddrinfo(host):
""" """
try: try:
return [addrinfo for addrinfo in socket.getaddrinfo(host, None) return [addrinfo for addrinfo in socket.getaddrinfo(host, None)
if (addrinfo[0] == socket.AF_INET or if (addrinfo[0] == socket.AF_INET or
addrinfo[0] == socket.AF_INET6) addrinfo[0] == socket.AF_INET6)
and isinstance(addrinfo[4][0], basestring)] and isinstance(addrinfo[4][0], basestring)]
except socket.error: except socket.error:
+39 -2
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)
@@ -3495,7 +3532,7 @@ class IS_IPV6(Validator):
('2001::8ffa:fe22:b3af', None) ('2001::8ffa:fe22:b3af', None)
>>> IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af') >>> IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
('2001::8ffa:fe22:b3af', 'invalid subnet provided') ('2001::8ffa:fe22:b3af', 'invalid subnet provided')
""" """
def __init__( def __init__(
+2 -2
View File
@@ -176,7 +176,7 @@ class Web2pyCronService(Web2pyService):
from gluon.settings import global_settings from gluon.settings import global_settings
from gluon.fileutils import abspath from gluon.fileutils import abspath
from os.path import exists, join from os.path import exists, join
self.log('web2py Cron service starting') self.log('web2py Cron service starting')
if not self.chdir(): if not self.chdir():
return return
@@ -186,7 +186,7 @@ class Web2pyCronService(Web2pyService):
opt_mod = self._exe_args_ opt_mod = self._exe_args_
options = __import__(opt_mod, [], [], '') options = __import__(opt_mod, [], [], '')
logpath = abspath(join(options.folder, "logging.conf")) logpath = abspath(join(options.folder, "logging.conf"))
if exists(logpath): if exists(logpath):
logging.config.fileConfig(logpath) logging.config.fileConfig(logpath)
else: else: