Merge pull request #1 from web2py/master

Syncing with upstream.
This commit is contained in:
Stephen Tanner
2014-10-24 14:35:48 -04:00
30 changed files with 925 additions and 262 deletions
+2
View File
@@ -49,6 +49,8 @@ applications/*/errors/*
applications/*/cache/* applications/*/cache/*
applications/*/uploads/* applications/*/uploads/*
applications/*/*.py[oc] applications/*/*.py[oc]
applications/*/static/temp
applications/*/progress.log
applications/examples/static/epydoc applications/examples/static/epydoc
applications/examples/static/sphinx applications/examples/static/sphinx
applications/admin/cron/cron.master applications/admin/cron/cron.master
+2
View File
@@ -22,6 +22,8 @@ before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi
- if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi - if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi
- if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi
- if [[ $DB == postgres* ]]; then psql -c 'create extension postgis;' -U postgres -d test_w2p; fi
# Install last sdk for app engine (update only whenever a new release is available) # Install last sdk for app engine (update only whenever a new release is available)
- if [[ $DB == google* ]]; then wget http://googleappengine.googlecode.com/files/google_appengine_1.8.9.zip -nv; fi - if [[ $DB == google* ]]; then wget http://googleappengine.googlecode.com/files/google_appengine_1.8.9.zip -nv; fi
+7
View File
@@ -24,6 +24,13 @@ epydoc:
cp applications/examples/static/title.png applications/examples/static/epydoc cp applications/examples/static/title.png applications/examples/static/epydoc
tests: tests:
python web2py.py --run_system_tests python web2py.py --run_system_tests
coverage:
coverage erase --rcfile=gluon/tests/coverage.ini
export COVERAGE_PROCESS_START=gluon/tests/coverage.ini
python web2py.py --run_system_tests --with_coverage
coverage combine --rcfile=gluon/tests/coverage.ini
sleep 1
coverage html --rcfile=gluon/tests/coverage.ini
update: update:
wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py
wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py
+1 -1
View File
@@ -1 +1 @@
Version 2.10.0-beta+timestamp.2014.09.24.13.35.58 Version 2.10.0-beta+timestamp.2014.10.16.15.58.50
+18 -28
View File
@@ -461,34 +461,24 @@ def ccache():
if value[0] < ram['oldest']: if value[0] < ram['oldest']:
ram['oldest'] = value[0] ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0]))) ram['keys'].append((key, GetInHMS(time.time() - value[0])))
folder = os.path.join(request.folder,'cache')
if not os.path.exists(folder): for key in cache.disk.storage:
os.mkdir(folder) value = cache.disk.storage[key]
locker = open(os.path.join(folder, 'cache.lock'), 'a') if isinstance(value, dict):
portalocker.lock(locker, portalocker.LOCK_EX) disk['hits'] = value['hit_total'] - value['misses']
disk_storage = shelve.open( disk['misses'] = value['misses']
os.path.join(folder, 'cache.shelve')) try:
try: disk['ratio'] = disk['hits'] * 100 / value['hit_total']
for key, value in disk_storage.items(): except (KeyError, ZeroDivisionError):
if isinstance(value, dict): disk['ratio'] = 0
disk['hits'] = value['hit_total'] - value['misses'] else:
disk['misses'] = value['misses'] if hp:
try: disk['bytes'] += hp.iso(value[1]).size
disk['ratio'] = disk['hits'] * 100 / value['hit_total'] disk['objects'] += hp.iso(value[1]).count
except (KeyError, ZeroDivisionError): disk['entries'] += 1
disk['ratio'] = 0 if value[0] < disk['oldest']:
else: disk['oldest'] = value[0]
if hp: disk['keys'].append((key, GetInHMS(time.time() - value[0])))
disk['bytes'] += hp.iso(value[1]).size
disk['objects'] += hp.iso(value[1]).count
disk['entries'] += 1
if value[0] < disk['oldest']:
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
finally:
portalocker.unlock(locker)
locker.close()
disk_storage.close()
total['entries'] = ram['entries'] + disk['entries'] total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes'] total['bytes'] = ram['bytes'] + disk['bytes']
+64 -12
View File
@@ -7,8 +7,10 @@
'%s %%{row} updated': '%s registros atualizados', '%s %%{row} updated': '%s registros atualizados',
'%Y-%m-%d': '%d/%m/%Y', '%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'(requires internet access)': '(requer acesso a internet)', '(requires internet access)': '(requer acesso à internet)',
'(requires internet access, experimental)': '(requer acesso à internet, experimental)',
'(something like "it-it")': '(algo como "it-it")', '(something like "it-it")': '(algo como "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page', '@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files', '@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available': 'Está disponível uma nova versão do web2py', 'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
@@ -16,7 +18,7 @@
'About': 'sobre', 'About': 'sobre',
'About application': 'Sobre a aplicação', 'About application': 'Sobre a aplicação',
'additional code for your application': 'código adicional para sua aplicação', 'additional code for your application': 'código adicional para sua aplicação',
'Additional code for your application': 'Additional code for your application', 'Additional code for your application': 'Código adicional para a sua aplicação',
'admin disabled because no admin password': ' admin desabilitado por falta de senha definida', 'admin disabled because no admin password': ' admin desabilitado por falta de senha definida',
'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE', 'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE',
'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha', 'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha',
@@ -33,6 +35,8 @@
'application compiled': 'aplicação compilada', 'application compiled': 'aplicação compilada',
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada', 'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
'Application name:': 'Nome da aplicação:', 'Application name:': 'Nome da aplicação:',
'are not used': 'não usadas',
'are not used yet': 'ainda não usadas',
'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?', 'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?', 'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', 'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
@@ -43,17 +47,20 @@
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.', 'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.',
'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!', 'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!',
'Autocomplete Python Code': 'Autocompletar Código Python',
'Available databases and tables': 'Bancos de dados e tabelas disponíveis', 'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
'back': 'voltar', 'back': 'voltar',
'browse': 'buscar', 'browse': 'buscar',
'cache': 'cache', 'cache': 'cache',
'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas', 'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas',
'can be a git repo': 'can be a git repo',
'Cannot be empty': 'Não pode ser vazio', 'Cannot be empty': 'Não pode ser vazio',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente',
'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação', 'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação',
'cannot create file': 'Não é possível criar o arquivo', 'cannot create file': 'Não é possível criar o arquivo',
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"', 'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
'Change admin password': 'mudar senha de administrador', 'Change admin password': 'mudar senha de administrador',
'change editor settings': 'mudar definições do editor',
'Change Password': 'Trocar Senha', 'Change Password': 'Trocar Senha',
'check all': 'marcar todos', 'check all': 'marcar todos',
'Check for upgrades': 'checar por atualizações', 'Check for upgrades': 'checar por atualizações',
@@ -67,7 +74,7 @@
'click to open': 'clique para abrir', 'click to open': 'clique para abrir',
'Client IP': 'IP do cliente', 'Client IP': 'IP do cliente',
'code': 'código', 'code': 'código',
'collapse/expand all': 'collapse/expand all', 'collapse/expand all': 'colapsar/expandir tudo',
'commit (mercurial)': 'commit (mercurial)', 'commit (mercurial)': 'commit (mercurial)',
'Compile': 'compilar', 'Compile': 'compilar',
'compiled application removed': 'aplicação compilada removida', 'compiled application removed': 'aplicação compilada removida',
@@ -79,6 +86,7 @@
'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente', 'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente',
'create new application:': 'nome da nova aplicação:', 'create new application:': 'nome da nova aplicação:',
'Create new simple application': 'Crie uma nova aplicação', 'Create new simple application': 'Crie uma nova aplicação',
'Create/Upload': 'Create/Upload',
'created by': 'criado por', 'created by': 'criado por',
'crontab': 'crontab', 'crontab': 'crontab',
'Current request': 'Requisição atual', 'Current request': 'Requisição atual',
@@ -99,18 +107,24 @@
'delete': 'apagar', 'delete': 'apagar',
'delete all checked': 'apagar marcados', 'delete all checked': 'apagar marcados',
'delete plugin': 'apagar plugin', 'delete plugin': 'apagar plugin',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Delete:': 'Apague:', 'Delete:': 'Apague:',
'Deploy': 'publicar', 'Deploy': 'publicar',
'Deploy on Google App Engine': 'Publicar no Google App Engine', 'Deploy on Google App Engine': 'Publicar no Google App Engine',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Description': 'Descrição', 'Description': 'Descrição',
'DESIGN': 'Projeto',
'design': 'modificar', 'design': 'modificar',
'DESIGN': 'Projeto',
'Design for': 'Projeto de', 'Design for': 'Projeto de',
'Detailed traceback description': 'Detailed traceback description', 'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direção: ltr', 'direction: ltr': 'direção: ltr',
'Disable': 'Disable',
'docs': 'docs',
'done!': 'feito!', 'done!': 'feito!',
'download layouts': 'download layouts', 'download layouts': 'download layouts',
'Download layouts from repository': 'Download layouts from repository',
'download plugins': 'download plugins', 'download plugins': 'download plugins',
'Download plugins from repository': 'Download plugins from repository',
'E-mail': 'E-mail', 'E-mail': 'E-mail',
'EDIT': 'EDITAR', 'EDIT': 'EDITAR',
'Edit': 'editar', 'Edit': 'editar',
@@ -119,6 +133,7 @@
'Edit current record': 'Editar o registro atual', 'Edit current record': 'Editar o registro atual',
'Edit Profile': 'Editar Perfil', 'Edit Profile': 'Editar Perfil',
'edit views:': 'editar visões:', 'edit views:': 'editar visões:',
'Editing %s': 'A Editar %s',
'Editing file': 'Editando arquivo', 'Editing file': 'Editando arquivo',
'Editing file "%s"': 'Editando arquivo "%s"', 'Editing file "%s"': 'Editando arquivo "%s"',
'Editing Language file': 'Editando arquivo de linguagem', 'Editing Language file': 'Editando arquivo de linguagem',
@@ -129,6 +144,8 @@
'Error ticket': 'Error ticket', 'Error ticket': 'Error ticket',
'Errors': 'erros', 'Errors': 'erros',
'Exception instance attributes': 'Atributos da instancia de excessão', 'Exception instance attributes': 'Atributos da instancia de excessão',
'Exit Fullscreen': 'Sair de Ecrã Inteiro',
'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)',
'export as csv file': 'exportar como arquivo CSV', 'export as csv file': 'exportar como arquivo CSV',
'exposes': 'expõe', 'exposes': 'expõe',
'extends': 'estende', 'extends': 'estende',
@@ -144,20 +161,24 @@
'file does not exist': 'arquivo não existe', 'file does not exist': 'arquivo não existe',
'file saved on %(time)s': 'arquivo salvo em %(time)s', 'file saved on %(time)s': 'arquivo salvo em %(time)s',
'file saved on %s': 'arquivo salvo em %s', 'file saved on %s': 'arquivo salvo em %s',
'filter': 'filter', 'filter': 'filtro',
'Find Next': 'Localizar Seguinte',
'Find Previous': 'Localizar Anterior',
'First name': 'Nome', 'First name': 'Nome',
'Frames': 'Frames', 'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].', 'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].',
'graph model': 'graph model',
'Group ID': 'ID do Grupo', 'Group ID': 'ID do Grupo',
'Hello World': 'Olá Mundo', 'Hello World': 'Olá Mundo',
'Help': 'ajuda', 'Help': 'ajuda',
'Hide/Show Translated strings': '',
'htmledit': 'htmledit', 'htmledit': 'htmledit',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.', 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.',
'Import/Export': 'Importar/Exportar', 'Import/Export': 'Importar/Exportar',
'includes': 'inclui', 'includes': 'inclui',
'insert new': 'inserir novo', 'insert new': 'inserir novo',
'insert new %s': 'inserir novo %s', 'insert new %s': 'inserir novo %s',
'inspect attributes': 'inspect attributes', 'inspect attributes': 'inspecionar atributos',
'Install': 'instalar', 'Install': 'instalar',
'Installed applications': 'Aplicações instaladas', 'Installed applications': 'Aplicações instaladas',
'internal error': 'erro interno', 'internal error': 'erro interno',
@@ -168,6 +189,7 @@
'Invalid Query': 'Consulta inválida', 'Invalid Query': 'Consulta inválida',
'invalid request': 'solicitação inválida', 'invalid request': 'solicitação inválida',
'invalid ticket': 'ticket inválido', 'invalid ticket': 'ticket inválido',
'Keyboard shortcuts': 'Atalhos de teclado',
'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado', 'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado',
'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados', 'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados',
'languages': 'linguagens', 'languages': 'linguagens',
@@ -178,11 +200,12 @@
'License for': 'Licença para', 'License for': 'Licença para',
'loading...': 'carregando...', 'loading...': 'carregando...',
'locals': 'locals', 'locals': 'locals',
'login': 'inicio de sessão',
'Login': 'Entrar', 'Login': 'Entrar',
'login': 'inicio de sessão',
'Login to the Administrative Interface': 'Entrar na interface adminitrativa', 'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
'Logout': 'finalizar sessão', 'Logout': 'finalizar sessão',
'Lost Password': 'Senha perdida', 'Lost Password': 'Senha perdida',
'Manage': 'Manage',
'manage': 'gerenciar', 'manage': 'gerenciar',
'merge': 'juntar', 'merge': 'juntar',
'Models': 'Modelos', 'Models': 'Modelos',
@@ -200,7 +223,10 @@
'NO': 'NÃO', 'NO': 'NÃO',
'No databases in this application': 'Não existem bancos de dados nesta aplicação', 'No databases in this application': 'Não existem bancos de dados nesta aplicação',
'no match': 'não encontrado', 'no match': 'não encontrado',
'no package selected': 'no package selected', 'no package selected': 'nenhum pacote selecionado',
'online designer': 'online designer',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Ou Obtenha do URL:',
'or import from csv file': 'ou importar de um arquivo CSV', 'or import from csv file': 'ou importar de um arquivo CSV',
'or provide app url:': 'ou forneça a url de uma aplicação:', 'or provide app url:': 'ou forneça a url de uma aplicação:',
'or provide application url:': 'ou forneça a url de uma aplicação:', 'or provide application url:': 'ou forneça a url de uma aplicação:',
@@ -209,6 +235,7 @@
'Overwrite installed app': 'sobrescrever aplicação instalada', 'Overwrite installed app': 'sobrescrever aplicação instalada',
'Pack all': 'criar pacote', 'Pack all': 'criar pacote',
'Pack compiled': 'criar pacote compilado', 'Pack compiled': 'criar pacote compilado',
'Pack custom': 'Pack custom',
'pack plugin': 'empacotar plugin', 'pack plugin': 'empacotar plugin',
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui', 'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
'Password': 'Senha', 'Password': 'Senha',
@@ -218,16 +245,23 @@
'Plugin "%s" in application': 'Plugin "%s" na aplicação', 'Plugin "%s" in application': 'Plugin "%s" na aplicação',
'plugins': 'plugins', 'plugins': 'plugins',
'Plugins': 'Plugins', 'Plugins': 'Plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Este site utiliza', 'Powered by': 'Este site utiliza',
'previous 100 rows': '100 registros anteriores', 'previous 100 rows': '100 registros anteriores',
'Private files': 'Private files',
'private files': 'private files',
'Query:': 'Consulta:', 'Query:': 'Consulta:',
'Rapid Search': 'Rapid Search',
'record': 'registro', 'record': 'registro',
'record does not exist': 'o registro não existe', 'record does not exist': 'o registro não existe',
'record id': 'id do registro', 'record id': 'id do registro',
'Record ID': 'ID do Registro', 'Record ID': 'ID do Registro',
'Register': 'Registrar-se', 'Register': 'Registrar-se',
'Registration key': 'Chave de registro', 'Registration key': 'Chave de registro',
'Reload routes': 'Reload routes',
'Remove compiled': 'eliminar compilados', 'Remove compiled': 'eliminar compilados',
'Replace': 'Substituir',
'Replace All': 'Substituir Tudo',
'request': 'request', 'request': 'request',
'Resolve Conflict file': 'Arquivo de resolução de conflito', 'Resolve Conflict file': 'Arquivo de resolução de conflito',
'response': 'response', 'response': 'response',
@@ -236,7 +270,14 @@
'Role': 'Papel', 'Role': 'Papel',
'Rows in table': 'Registros na tabela', 'Rows in table': 'Registros na tabela',
'Rows selected': 'Registros selecionados', 'Rows selected': 'Registros selecionados',
'rules are not defined': 'rules are not defined',
"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': 'A correr em %s',
'Save': 'Save',
'save': 'salvar', 'save': 'salvar',
'Save file:': 'Gravar ficheiro:',
'Save file: %s': 'Gravar ficheiro: %s',
'Save via Ajax': 'Gravar via Ajax',
'Saved file hash:': 'Hash do arquivo salvo:', 'Saved file hash:': 'Hash do arquivo salvo:',
'selected': 'selecionado(s)', 'selected': 'selecionado(s)',
'session': 'session', 'session': 'session',
@@ -244,10 +285,13 @@
'shell': 'Terminal', 'shell': 'Terminal',
'Site': 'site', 'Site': 'site',
'some files could not be removed': 'alguns arquicos não puderam ser removidos', 'some files could not be removed': 'alguns arquicos não puderam ser removidos',
'Start searching': 'Start searching',
'Start wizard': 'iniciar assistente', 'Start wizard': 'iniciar assistente',
'state': 'estado', 'state': 'estado',
'Static': 'Static',
'static': 'estáticos', 'static': 'estáticos',
'Static files': 'Arquivos estáticos', 'Static files': 'Arquivos estáticos',
'Submit': 'Submit',
'submit': 'enviar', 'submit': 'enviar',
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?', 'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
'table': 'tabela', 'table': 'tabela',
@@ -255,21 +299,23 @@
'test': 'testar', 'test': 'testar',
'Testing application': 'Testando a aplicação', 'Testing application': 'Testando a aplicação',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.', 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador', 'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets', 'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados', 'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates', 'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates', 'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates',
'There are no controllers': 'Não existem controllers', 'There are no controllers': 'Não existem controllers',
'There are no models': 'Não existem modelos', 'There are no models': 'Não existem modelos',
'There are no modules': 'Não existem módulos', 'There are no modules': 'Não existem módulos',
'There are no plugins': 'There are no plugins', 'There are no plugins': 'There are no plugins',
'There are no private files': '',
'There are no static files': 'Não existem arquicos estáticos', 'There are no static files': 'Não existem arquicos estáticos',
'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada', 'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada',
'There are no views': 'Não existem visões', 'There are no views': 'Não existem visões',
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui', 'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here', 'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
'This is the %(filename)s template': 'Este é o template %(filename)s', 'This is the %(filename)s template': 'Este é o template %(filename)s',
'Ticket': 'Ticket', 'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID', 'Ticket ID': 'Ticket ID',
@@ -277,11 +323,15 @@
'TM': 'MR', 'TM': 'MR',
'to previous version.': 'para a versão anterior.', 'to previous version.': 'para a versão anterior.',
'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]', 'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle comment': 'Toggle comment',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback', 'Traceback': 'Traceback',
'translation strings for the application': 'textos traduzidos para a aplicação', 'translation strings for the application': 'textos traduzidos para a aplicação',
'Translation strings for the application': 'Translation strings for the application', 'Translation strings for the application': 'Translation strings for the application',
'try': 'tente', 'try': 'tente',
'try something like': 'tente algo como', 'try something like': 'tente algo como',
'Try the mobile interface': 'Try the mobile interface',
'Unable to check for upgrades': 'Não é possível checar as atualizações', 'Unable to check for upgrades': 'Não é possível checar as atualizações',
'unable to create application "%s"': 'não é possível criar a aplicação "%s"', 'unable to create application "%s"': 'não é possível criar a aplicação "%s"',
'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"', 'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"',
@@ -300,8 +350,10 @@
'Update:': 'Atualizar:', 'Update:': 'Atualizar:',
'upgrade web2py now': 'atualize o web2py agora', 'upgrade web2py now': 'atualize o web2py agora',
'upload': 'upload', 'upload': 'upload',
'Upload': 'Upload',
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada', 'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
'Upload a package:': 'Faça upload de um pacote:', 'Upload a package:': 'Faça upload de um pacote:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'Fazer upload de uma aplicação:', 'upload application:': 'Fazer upload de uma aplicação:',
'Upload existing application': 'Faça upload de uma aplicação existente', 'Upload existing application': 'Faça upload de uma aplicação existente',
'upload file:': 'Enviar arquivo:', 'upload file:': 'Enviar arquivo:',
+12 -10
View File
@@ -141,7 +141,7 @@
}, },
/* manage errors in forms */ /* manage errors in forms */
manage_errors: function(target) { manage_errors: function(target) {
$('.error', target).hide().slideDown('slow'); $('div.error', target).hide().slideDown('slow');
}, },
after_ajax: function(xhr) { after_ajax: function(xhr) {
/* called whenever an ajax request completes */ /* called whenever an ajax request completes */
@@ -684,19 +684,21 @@
} }
}); });
$.web2py.component_handler(target); $.web2py.component_handler(target);
},
main_hook : function() {
var flash = $('.flash');
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);
web2py.event_handlers();
web2py.a_handlers();
web2py.form_handlers();
} }
} }
/*end of functions */ /*end of functions */
/*main hook*/ /*main hook*/
$(function() { $(function () {
var flash = $('.flash'); web2py.main_hook();
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);
web2py.event_handlers();
web2py.a_handlers();
web2py.form_handlers();
}); });
})(jQuery); })(jQuery);
@@ -4,12 +4,10 @@ frm = form
smt_button = frm.element(_type="submit") smt_button = frm.element(_type="submit")
smt_button['_class'] = 'btn' smt_button['_class'] = 'btn'
smt_button['_style'] = 'margin-right:4px;' smt_button['_style'] = 'margin-right:4px;'
ccl_button = frm.element(_type="button")
ccl_button['_class'] = 'btn'
}} }}
<!-- begin "git_push" block --> <!-- begin "git_push" block -->
<h2>{{=T('This will push changes to the remote repo for application "%s".', app)}}</h2> <h2>{{=T('This will push changes to the remote repo for application "%s".', app)}}</h2>
<center> <center>
{{=form}} {{=form}}
</center> </center>
<!-- end "git_push" block --> <!-- end "git_push" block -->
+12 -10
View File
@@ -141,7 +141,7 @@
}, },
/* manage errors in forms */ /* manage errors in forms */
manage_errors: function(target) { manage_errors: function(target) {
$('.error', target).hide().slideDown('slow'); $('div.error', target).hide().slideDown('slow');
}, },
after_ajax: function(xhr) { after_ajax: function(xhr) {
/* called whenever an ajax request completes */ /* called whenever an ajax request completes */
@@ -684,19 +684,21 @@
} }
}); });
$.web2py.component_handler(target); $.web2py.component_handler(target);
},
main_hook : function() {
var flash = $('.flash');
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);
web2py.event_handlers();
web2py.a_handlers();
web2py.form_handlers();
} }
} }
/*end of functions */ /*end of functions */
/*main hook*/ /*main hook*/
$(function() { $(function () {
var flash = $('.flash'); web2py.main_hook();
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);
web2py.event_handlers();
web2py.a_handlers();
web2py.form_handlers();
}); });
})(jQuery); })(jQuery);
+18 -28
View File
@@ -461,34 +461,24 @@ def ccache():
if value[0] < ram['oldest']: if value[0] < ram['oldest']:
ram['oldest'] = value[0] ram['oldest'] = value[0]
ram['keys'].append((key, GetInHMS(time.time() - value[0]))) ram['keys'].append((key, GetInHMS(time.time() - value[0])))
folder = os.path.join(request.folder,'cache')
if not os.path.exists(folder): for key in cache.disk.storage:
os.mkdir(folder) value = cache.disk.storage[key]
locker = open(os.path.join(folder, 'cache.lock'), 'a') if isinstance(value, dict):
portalocker.lock(locker, portalocker.LOCK_EX) disk['hits'] = value['hit_total'] - value['misses']
disk_storage = shelve.open( disk['misses'] = value['misses']
os.path.join(folder, 'cache.shelve')) try:
try: disk['ratio'] = disk['hits'] * 100 / value['hit_total']
for key, value in disk_storage.items(): except (KeyError, ZeroDivisionError):
if isinstance(value, dict): disk['ratio'] = 0
disk['hits'] = value['hit_total'] - value['misses'] else:
disk['misses'] = value['misses'] if hp:
try: disk['bytes'] += hp.iso(value[1]).size
disk['ratio'] = disk['hits'] * 100 / value['hit_total'] disk['objects'] += hp.iso(value[1]).count
except (KeyError, ZeroDivisionError): disk['entries'] += 1
disk['ratio'] = 0 if value[0] < disk['oldest']:
else: disk['oldest'] = value[0]
if hp: disk['keys'].append((key, GetInHMS(time.time() - value[0])))
disk['bytes'] += hp.iso(value[1]).size
disk['objects'] += hp.iso(value[1]).count
disk['entries'] += 1
if value[0] < disk['oldest']:
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
finally:
portalocker.unlock(locker)
locker.close()
disk_storage.close()
total['entries'] = ram['entries'] + disk['entries'] total['entries'] = ram['entries'] + disk['entries']
total['bytes'] = ram['bytes'] + disk['bytes'] total['bytes'] = ram['bytes'] + disk['bytes']
+12 -10
View File
@@ -141,7 +141,7 @@
}, },
/* manage errors in forms */ /* manage errors in forms */
manage_errors: function(target) { manage_errors: function(target) {
$('.error', target).hide().slideDown('slow'); $('div.error', target).hide().slideDown('slow');
}, },
after_ajax: function(xhr) { after_ajax: function(xhr) {
/* called whenever an ajax request completes */ /* called whenever an ajax request completes */
@@ -684,19 +684,21 @@
} }
}); });
$.web2py.component_handler(target); $.web2py.component_handler(target);
},
main_hook : function() {
var flash = $('.flash');
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);
web2py.event_handlers();
web2py.a_handlers();
web2py.form_handlers();
} }
} }
/*end of functions */ /*end of functions */
/*main hook*/ /*main hook*/
$(function() { $(function () {
var flash = $('.flash'); web2py.main_hook();
flash.hide();
if(flash.html()) web2py.flash(flash.html());
web2py.ajax_init(document);
web2py.event_handlers();
web2py.a_handlers();
web2py.form_handlers();
}); });
})(jQuery); })(jQuery);
+150
View File
@@ -0,0 +1,150 @@
gluon.dal.adapters package
==========================
Submodules
----------
gluon.dal.adapters.base module
------------------------------
.. automodule:: gluon.dal.adapters.base
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.couchdb module
---------------------------------
.. automodule:: gluon.dal.adapters.couchdb
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.cubrid module
--------------------------------
.. automodule:: gluon.dal.adapters.cubrid
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.db2 module
-----------------------------
.. automodule:: gluon.dal.adapters.db2
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.firebird module
----------------------------------
.. automodule:: gluon.dal.adapters.firebird
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.google module
--------------------------------
.. automodule:: gluon.dal.adapters.google
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.imap module
------------------------------
.. automodule:: gluon.dal.adapters.imap
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.informix module
----------------------------------
.. automodule:: gluon.dal.adapters.informix
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.ingres module
--------------------------------
.. automodule:: gluon.dal.adapters.ingres
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.mongo module
-------------------------------
.. automodule:: gluon.dal.adapters.mongo
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.mssql module
-------------------------------
.. automodule:: gluon.dal.adapters.mssql
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.mysql module
-------------------------------
.. automodule:: gluon.dal.adapters.mysql
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.oracle module
--------------------------------
.. automodule:: gluon.dal.adapters.oracle
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.postgre module
---------------------------------
.. automodule:: gluon.dal.adapters.postgre
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.sapdb module
-------------------------------
.. automodule:: gluon.dal.adapters.sapdb
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.sqlite module
--------------------------------
.. automodule:: gluon.dal.adapters.sqlite
:members:
:undoc-members:
:show-inheritance:
gluon.dal.adapters.teradata module
----------------------------------
.. automodule:: gluon.dal.adapters.teradata
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: gluon.dal.adapters
:members:
:undoc-members:
:show-inheritance:
+38
View File
@@ -0,0 +1,38 @@
gluon.dal.helpers package
=========================
Submodules
----------
gluon.dal.helpers.classes module
--------------------------------
.. automodule:: gluon.dal.helpers.classes
:members:
:undoc-members:
:show-inheritance:
gluon.dal.helpers.methods module
--------------------------------
.. automodule:: gluon.dal.helpers.methods
:members:
:undoc-members:
:show-inheritance:
gluon.dal.helpers.regex module
------------------------------
.. automodule:: gluon.dal.helpers.regex
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: gluon.dal.helpers
:members:
:undoc-members:
:show-inheritance:
+40 -2
View File
@@ -1,6 +1,44 @@
gluon.dal package
=================
:mod:`dal` Module Subpackages
----------------- -----------
.. toctree::
dal.adapters
dal.helpers
Submodules
----------
gluon.dal.base module
---------------------
.. automodule:: gluon.dal.base
:members:
:undoc-members:
:show-inheritance:
gluon.dal.connection module
---------------------------
.. automodule:: gluon.dal.connection
:members:
:undoc-members:
:show-inheritance:
gluon.dal.objects module
------------------------
.. automodule:: gluon.dal.objects
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: gluon.dal .. automodule:: gluon.dal
:members: :members:
+27 -10
View File
@@ -70,8 +70,8 @@ Here is a complete sample web2py action:
'http://127.0.0.1:8888', form.vars.message, 'mykey', 'mygroup') 'http://127.0.0.1:8888', form.vars.message, 'mykey', 'mygroup')
return form return form
https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', https is possible too using 'https://127.0.0.1:8888' instead of 'http://127.0.0.1:8888', but need to
but needs to be started with be started with
python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem python gluon/contrib/websocket_messaging.py -k mykey -p 8888 -s keyfile.pem -c certfile.pem
@@ -113,7 +113,7 @@ class PostHandler(tornado.web.RequestHandler):
""" """
def post(self): def post(self):
if hmac_key and not 'signature' in self.request.arguments: if hmac_key and not 'signature' in self.request.arguments:
return None self.send_error(401)
if 'message' in self.request.arguments: if 'message' in self.request.arguments:
message = self.request.arguments['message'][0] message = self.request.arguments['message'][0]
group = self.request.arguments.get('group', ['default'])[0] group = self.request.arguments.get('group', ['default'])[0]
@@ -121,11 +121,9 @@ class PostHandler(tornado.web.RequestHandler):
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key, message).hexdigest() == signature: if not hmac.new(hmac_key, message).hexdigest() == signature:
return None self.send_error(401)
for client in listeners.get(group, []): for client in listeners.get(group, []):
client.write_message(message) client.write_message(message)
return None
return 'false'
class TokenHandler(tornado.web.RequestHandler): class TokenHandler(tornado.web.RequestHandler):
@@ -136,15 +134,14 @@ class TokenHandler(tornado.web.RequestHandler):
""" """
def post(self): def post(self):
if hmac_key and not 'message' in self.request.arguments: if hmac_key and not 'message' in self.request.arguments:
return None self.send_error(401)
if 'message' in self.request.arguments: if 'message' in self.request.arguments:
message = self.request.arguments['message'][0] message = self.request.arguments['message'][0]
if hmac_key: if hmac_key:
signature = self.request.arguments['signature'][0] signature = self.request.arguments['signature'][0]
if not hmac.new(hmac_key, message).hexdigest() == signature: if not hmac.new(hmac_key, message).hexdigest() == signature:
return None self.send_error(401)
tokens[message] = None tokens[message] = None
return None
class DistributeHandler(tornado.websocket.WebSocketHandler): class DistributeHandler(tornado.websocket.WebSocketHandler):
@@ -180,6 +177,12 @@ class DistributeHandler(tornado.websocket.WebSocketHandler):
client.write_message('-' + self.name) client.write_message('-' + self.name)
print '%s:DISCONNECT from %s' % (time.time(), self.group) print '%s:DISCONNECT from %s' % (time.time(), self.group)
# if your webserver is different from tornado server uncomment this
# or override using something more restrictive:
# http://tornado.readthedocs.org/en/latest/websocket.html#tornado.websocket.WebSocketHandler.check_origin
# def check_origin(self, origin):
# return True
if __name__ == "__main__": if __name__ == "__main__":
usage = __doc__ usage = __doc__
version = "" version = ""
@@ -205,6 +208,16 @@ if __name__ == "__main__":
default=False, default=False,
dest='tokens', dest='tokens',
help='require tockens to join') help='require tockens to join')
parser.add_option('-s',
'--sslkey',
default=False,
dest='keyfile',
help='require ssl keyfile full path')
parser.add_option('-c',
'--sslcert',
default=False,
dest='certfile',
help='require ssl certfile full path')
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
hmac_key = options.hmac_key hmac_key = options.hmac_key
DistributeHandler.tokens = options.tokens DistributeHandler.tokens = options.tokens
@@ -213,6 +226,10 @@ if __name__ == "__main__":
(r'/token', TokenHandler), (r'/token', TokenHandler),
(r'/realtime/(.*)', DistributeHandler)] (r'/realtime/(.*)', DistributeHandler)]
application = tornado.web.Application(urls, auto_reload=True) application = tornado.web.Application(urls, auto_reload=True)
http_server = tornado.httpserver.HTTPServer(application) if options.keyfile and options.certfile:
ssl_options = dict(certfile=options.certfile, keyfile=options.keyfile)
else:
ssl_options = None
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options)
http_server.listen(int(options.port), address=options.address) http_server.listen(int(options.port), address=options.address)
tornado.ioloop.IOLoop.instance().start() tornado.ioloop.IOLoop.instance().start()
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .sqlite import SQLiteAdapter, SpatiaLiteAdapter, JDBCSQLiteAdapter from .sqlite import SQLiteAdapter, SpatiaLiteAdapter, JDBCSQLiteAdapter
from .mysql import MySQLAdapter from .mysql import MySQLAdapter
from .postgre import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter from .postgres import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter
from .oracle import OracleAdapter from .oracle import OracleAdapter
from .mssql import MSSQLAdapter, MSSQL2Adapter, MSSQL3Adapter, MSSQL4Adapter, \ from .mssql import MSSQLAdapter, MSSQL2Adapter, MSSQL3Adapter, MSSQL4Adapter, \
VerticaAdapter, SybaseAdapter VerticaAdapter, SybaseAdapter
+16 -14
View File
@@ -515,24 +515,26 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
""" """
This is the GAE version of select. Some notes to consider: This is the GAE version of select. Some notes to consider:
- db['_lastsql'] is not set because there is not SQL statement string - db['_lastsql'] is not set because there is not SQL statement string
for a GAE query for a GAE query
- 'nativeRef' is a magical fieldname used for self references on GAE - 'nativeRef' is a magical fieldname used for self references on GAE
- optional attribute 'projection' when set to True will trigger - optional attribute 'projection' when set to True will trigger
use of the GAE projection queries. note that there are rules for use of the GAE projection queries. note that there are rules for
what is accepted imposed by GAE: each field must be indexed, what is accepted imposed by GAE: each field must be indexed,
projection queries cannot contain blob or text fields, and you projection queries cannot contain blob or text fields, and you
cannot use == and also select that same field. see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection cannot use == and also select that same field.
see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection
- optional attribute 'filterfields' when set to True web2py will only - optional attribute 'filterfields' when set to True web2py will only
parse the explicitly listed fields into the Rows object, even though parse the explicitly listed fields into the Rows object, even though
all fields are returned in the query. This can be used to reduce all fields are returned in the query. This can be used to reduce
memory usage in cases where true projection queries are not memory usage in cases where true projection queries are not
usable. usable.
- optional attribute 'reusecursor' allows use of cursor with queries - optional attribute 'reusecursor' allows use of cursor with queries
that have the limitby attribute. Set the attribute to True for the that have the limitby attribute. Set the attribute to True for the
first query, set it to the value of db['_lastcursor'] to continue first query, set it to the value of db['_lastcursor'] to continue
a previous query. The user must save the cursor value between a previous query. The user must save the cursor value between
requests, and the filters must be identical. It is up to the user requests, and the filters must be identical. It is up to the user
to follow google's limitations: https://developers.google.com/appengine/docs/python/datastore/queries#Query_Cursors to follow google's limitations:
https://developers.google.com/appengine/docs/python/datastore/queries#Query_Cursors
""" """
(items, tablename, fields) = self.select_raw(query,fields,attributes) (items, tablename, fields) = self.select_raw(query,fields,attributes)
+26 -25
View File
@@ -12,7 +12,6 @@ from .base import NoSQLAdapter
class IMAPAdapter(NoSQLAdapter): class IMAPAdapter(NoSQLAdapter):
drivers = ('imaplib',)
""" IMAP server adapter """ IMAP server adapter
@@ -44,29 +43,31 @@ class IMAPAdapter(NoSQLAdapter):
Here is a list of supported fields: Here is a list of supported fields:
Field Type Description =========== ============== ===========
################################################################ Field Type Description
uid string =========== ============== ===========
answered boolean Flag uid string
created date answered boolean Flag
content list:string A list of dict text or html parts created date
to string content list:string A list of dict text or html parts
cc string to string
bcc string cc string
size integer the amount of octets of the message* bcc string
deleted boolean Flag size integer the amount of octets of the message*
draft boolean Flag deleted boolean Flag
flagged boolean Flag draft boolean Flag
sender string flagged boolean Flag
recent boolean Flag sender string
seen boolean Flag recent boolean Flag
subject string seen boolean Flag
mime string The mime header declaration subject string
email string The complete RFC822 message** mime string The mime header declaration
attachments <type list> Each non text part as dict email string The complete RFC822 message (*)
encoding string The main detected encoding attachments list Each non text part as dict
encoding string The main detected encoding
*At the application side it is measured as the length of the RFC822 =========== ============== ===========
(*) At the application side it is measured as the length of the RFC822
message string message string
WARNING: As row id's are mapped to email sequence numbers, WARNING: As row id's are mapped to email sequence numbers,
@@ -141,7 +142,7 @@ class IMAPAdapter(NoSQLAdapter):
imapdb(q).select(imapdb.INBOX.sender, imapdb.INBOX.subject) imapdb(q).select(imapdb.INBOX.sender, imapdb.INBOX.subject)
""" """
drivers = ('imaplib',)
types = { types = {
'string': str, 'string': str,
'text': str, 'text': str,
@@ -173,9 +173,12 @@ class PostgreSQLAdapter(BaseAdapter):
def try_json(self): def try_json(self):
# check JSON data type support # check JSON data type support
# (to be added to after_connection) # (to be added to after_connection)
if self.driver_name == "pg8000":
supports_json = self.connection.server_version >= "9.2.0" # until pg8000 supports json, leave this commented
elif (self.driver_name == "psycopg2" and #if self.driver_name == "pg8000":
# supports_json = self.connection.server_version >= "9.2.0"
if (self.driver_name == "psycopg2" and
self.driver.__version__ >= "2.0.12"): self.driver.__version__ >= "2.0.12"):
supports_json = self.connection.server_version >= 90200 supports_json = self.connection.server_version >= 90200
elif self.driver_name == "zxJDBC": elif self.driver_name == "zxJDBC":
@@ -211,14 +214,6 @@ class PostgreSQLAdapter(BaseAdapter):
return '(%s ~ %s)' % (self.expand(first), return '(%s ~ %s)' % (self.expand(first),
self.expand(second,'string')) self.expand(second,'string'))
def STARTSWITH(self,first,second):
return '(%s LIKE %s)' % (self.expand(first),
self.expand(second+'%','string'))
def ENDSWITH(self,first,second):
return '(%s LIKE %s)' % (self.expand(first),
self.expand('%'+second,'string'))
# GIS functions # GIS functions
def ST_ASGEOJSON(self, first, second): def ST_ASGEOJSON(self, first, second):
+1
View File
@@ -234,6 +234,7 @@ class DAL(object):
""" """
Table = Table
def __new__(cls, uri='sqlite://dummy.db', *args, **kwargs): def __new__(cls, uri='sqlite://dummy.db', *args, **kwargs):
if not hasattr(THREAD_LOCAL,'db_instances'): if not hasattr(THREAD_LOCAL,'db_instances'):
+1 -1
View File
@@ -1208,7 +1208,7 @@ class Expression(object):
return Query(db, op, self, value) return Query(db, op, self, value)
def ilike(self, value): def ilike(self, value):
return self.like(case_sensitive=False) return self.like(value, case_sensitive=False)
def regexp(self, value): def regexp(self, value):
db = self.db db = self.db
+5 -14
View File
@@ -10,8 +10,7 @@ Cross-site scripting (XSS) defense
----------------------------------- -----------------------------------
""" """
from HTMLParser import HTMLParser
from htmllib import HTMLParser
from cgi import escape from cgi import escape
from urlparse import urlparse from urlparse import urlparse
from formatter import AbstractFormatter from formatter import AbstractFormatter
@@ -48,11 +47,10 @@ class XssCleaner(HTMLParser):
], ],
allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt' allowed_attributes={'a': ['href', 'title'], 'img': ['src', 'alt'
], 'blockquote': ['type']}, ], 'blockquote': ['type']},
fmt=AbstractFormatter,
strip_disallowed=False strip_disallowed=False
): ):
HTMLParser.__init__(self, fmt) HTMLParser.__init__(self)
self.result = '' self.result = ''
self.open_tags = [] self.open_tags = []
self.permitted_tags = [i for i in permitted_tags if i[-1] != '/'] self.permitted_tags = [i for i in permitted_tags if i[-1] != '/']
@@ -77,7 +75,7 @@ class XssCleaner(HTMLParser):
def handle_charref(self, ref): def handle_charref(self, ref):
if self.in_disallowed: if self.in_disallowed:
return return
elif len(ref) < 7 and ref.isdigit(): elif len(ref) < 7 and (ref.isdigit() or ref == 'x27'): # x27 is a special case for apostrophe
self.result += '&#%s;' % ref self.result += '&#%s;' % ref
else: else:
self.result += xssescape('&#%s' % ref) self.result += xssescape('&#%s' % ref)
@@ -99,8 +97,7 @@ class XssCleaner(HTMLParser):
def handle_starttag( def handle_starttag(
self, self,
tag, tag,
method, attrs
attrs,
): ):
if tag not in self.permitted_tags: if tag not in self.permitted_tags:
if self.strip_disallowed: if self.strip_disallowed:
@@ -130,7 +127,7 @@ class XssCleaner(HTMLParser):
self.result += bt self.result += bt
self.open_tags.insert(0, tag) self.open_tags.insert(0, tag)
def handle_endtag(self, tag, attrs): def handle_endtag(self, tag):
bracketed = '</%s>' % tag bracketed = '</%s>' % tag
if tag not in self.permitted_tags: if tag not in self.permitted_tags:
if self.strip_disallowed: if self.strip_disallowed:
@@ -141,12 +138,6 @@ class XssCleaner(HTMLParser):
self.result += bracketed self.result += bracketed
self.open_tags.remove(tag) self.open_tags.remove(tag)
def unknown_starttag(self, tag, attributes):
self.handle_starttag(tag, None, attributes)
def unknown_endtag(self, tag):
self.handle_endtag(tag, None)
def url_is_acceptable(self, url): def url_is_acceptable(self, url):
""" """
Accepts relative, absolute, and mailto urls Accepts relative, absolute, and mailto urls
+105 -71
View File
@@ -14,13 +14,18 @@ Holds:
""" """
import datetime
import urllib
import re
import cStringIO
import os import os
from gluon.http import HTTP from gluon.http import HTTP, redirect
from gluon.html import XmlComponent from gluon.html import XmlComponent, truncate_string
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT, P from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, DEFAULT_PASSWORD_DISPLAY from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
from gluon.html import URL, truncate_string, FIELDSET from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
from gluon.dal.base import DEFAULT from gluon.dal.base import DEFAULT
from gluon.dal.objects import Table, Row, Expression from gluon.dal.objects import Table, Row, Expression
@@ -29,16 +34,12 @@ from gluon.dal.helpers.methods import smart_query, bar_encode, sqlhtml_validator
from gluon.dal.helpers.classes import Reference, SQLCustomType from gluon.dal.helpers.classes import Reference, SQLCustomType
from gluon.storage import Storage from gluon.storage import Storage
from gluon.utils import md5_hash from gluon.utils import md5_hash
from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \ from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG from gluon.validators import IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE
from gluon.validators import IS_STRONG
import gluon.serializers as serializers import gluon.serializers as serializers
import datetime
import urllib
import re
import cStringIO
from gluon.globals import current from gluon.globals import current
from gluon.http import redirect
try: try:
import gluon.settings as settings import gluon.settings as settings
@@ -49,7 +50,7 @@ widget_class = re.compile('^\w*')
def add_class(a, b): def add_class(a, b):
return a+' '+b if a else b return a + ' ' + b if a else b
def represent(field, value, record): def represent(field, value, record):
@@ -85,11 +86,11 @@ def show_if(cond):
if not cond: if not cond:
return None return None
base = "%s_%s" % (cond.first.tablename, cond.first.name) base = "%s_%s" % (cond.first.tablename, cond.first.name)
if ((cond.op.__name__ == 'EQ' and cond.second == True) or if ((cond.op.__name__ == 'EQ' and cond.second is True) or
(cond.op.__name__ == 'NE' and cond.second == False)): (cond.op.__name__ == 'NE' and cond.second is False)):
return base, ":checked" return base, ":checked"
if ((cond.op.__name__ == 'EQ' and cond.second == False) or if ((cond.op.__name__ == 'EQ' and cond.second is False) or
(cond.op.__name__ == 'NE' and cond.second == True)): (cond.op.__name__ == 'NE' and cond.second is True)):
return base, ":not(:checked)" return base, ":not(:checked)"
if cond.op.__name__ == 'EQ': if cond.op.__name__ == 'EQ':
return base, "[value='%s']" % cond.second return base, "[value='%s']" % cond.second
@@ -305,7 +306,8 @@ 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] 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,
@@ -351,7 +353,6 @@ class RadioWidget(OptionsWidget):
else: else:
value = str(value) value = str(value)
attr = cls._attributes(field, {}, **attributes) attr = cls._attributes(field, {}, **attributes)
attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget') attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget')
@@ -626,7 +627,7 @@ class AutocompleteWidget(object):
self.help_fields = help_fields or [] self.help_fields = help_fields or []
self.help_string = help_string self.help_string = help_string
if self.help_fields and not self.help_string: if self.help_fields and not self.help_string:
self.help_string = ' '.join('%%(%s)s'%f.name self.help_string = ' '.join('%%(%s)s' % f.name
for f in self.help_fields) for f in self.help_fields)
self.request = request self.request = request
@@ -900,7 +901,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
label['_for'] = None label['_for'] = None
label.insert(0, controls) label.insert(0, controls)
_controls = DIV(DIV(label, _help, _class="checkbox"), _controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class)) _class="%s %s" % (offset_class, col_class))
label = '' label = ''
elif isinstance(controls, SELECT): elif isinstance(controls, SELECT):
controls.add_class('form-control') controls.add_class('form-control')
@@ -1132,7 +1133,7 @@ class SQLFORM(FORM):
if fieldname.find('.') >= 0: if fieldname.find('.') >= 0:
continue continue
field = (self.table[fieldname] if fieldname in self.table.fields field = (self.table[fieldname] if fieldname in self.table.fields
else self.extra_fields[fieldname]) else self.extra_fields[fieldname])
comment = None comment = None
@@ -1184,7 +1185,7 @@ class SQLFORM(FORM):
cond = readonly or \ cond = readonly or \
(not ignore_rw and not field.writable and field.readable) (not ignore_rw and not field.writable and field.readable)
if default is not None and not cond: if default is not None and not cond:
default = field.formatter(default) default = field.formatter(default)
dspval = default dspval = default
@@ -1192,10 +1193,6 @@ class SQLFORM(FORM):
if cond: if cond:
# ## if field.re field.requires = sqlhtml_validators(field) field.requires = sqlhtml_validators(field)present is available else
# ## ignore blob and preview uploaded images
# ## format everything else
if field.represent: if field.represent:
inp = represent(field, default, record) inp = represent(field, default, record)
elif field.type in ['blob']: elif field.type in ['blob']:
@@ -1272,7 +1269,7 @@ class SQLFORM(FORM):
(olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX, (olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX,
'', widget, col3.get(olname, ''))) '', widget, col3.get(olname, '')))
self.custom.linkto[olname.replace('.', '__')] = widget self.custom.linkto[olname.replace('.', '__')] = widget
# </block> # </block>
# when deletable, add delete? checkbox # when deletable, add delete? checkbox
self.custom.delete = self.custom.deletable = '' self.custom.delete = self.custom.deletable = ''
@@ -1291,16 +1288,15 @@ class SQLFORM(FORM):
) )
xfields.append( xfields.append(
(self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX, (self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX,
LABEL( LABEL(
T(delete_label), sep, T(delete_label), sep,
_for=self.FIELDKEY_DELETE_RECORD, _for=self.FIELDKEY_DELETE_RECORD,
_id=self.FIELDKEY_DELETE_RECORD + \ _id=self.FIELDKEY_DELETE_RECORD +
SQLFORM.ID_LABEL_SUFFIX), SQLFORM.ID_LABEL_SUFFIX),
widget, widget,
col3.get(self.FIELDKEY_DELETE_RECORD, ''))) col3.get(self.FIELDKEY_DELETE_RECORD, '')))
self.custom.delete = self.custom.deletable = widget self.custom.delete = self.custom.deletable = widget
# when writable, add submit button # when writable, add submit button
self.custom.submit = '' self.custom.submit = ''
if not readonly: if not readonly:
@@ -1480,7 +1476,7 @@ class SQLFORM(FORM):
# that does not pass validation, yet it should be deleted # that does not pass validation, yet it should be deleted
for fieldname in self.fields: for fieldname in self.fields:
field = (self.table[fieldname] field = (self.table[fieldname]
if fieldname in self.table.fields if fieldname in self.table.fields
else self.extra_fields[fieldname]) else self.extra_fields[fieldname])
### this is a workaround! widgets should always have default not None! ### this is a workaround! widgets should always have default not None!
@@ -1995,6 +1991,16 @@ class SQLFORM(FORM):
details = details and not groupby details = details and not groupby
rows = None rows = None
# see issue 1980. Basically we can have keywords in get_vars
# (i.e. when the search term is propagated through page=2&keywords=abc)
# but if there is keywords in post_vars (i.e. POSTing a search request)
# the one in get_vars should be replaced by the new one
keywords = ''
if 'keywords' in request.post_vars:
keywords = request.post_vars.keywords
elif 'keywords' in request.get_vars:
keywords = request.get_vars.keywords
def fetch_count(dbset): def fetch_count(dbset):
##FIXME for google:datastore cache_count is ignored ##FIXME for google:datastore cache_count is ignored
## if it's not an integer ## if it's not an integer
@@ -2051,7 +2057,7 @@ class SQLFORM(FORM):
'/'.join(str(a) for a in args) == '/'.join(request.args) or '/'.join(str(a) for a in args) == '/'.join(request.args) or
URL.verify(request, user_signature=user_signature, URL.verify(request, user_signature=user_signature,
hash_vars=False) or hash_vars=False) or
(request.args(len(args))=='view' and not logged)): (request.args(len(args))=='view' and not logged)):
session.flash = T('not authorized') session.flash = T('not authorized')
redirect(referrer) redirect(referrer)
@@ -2096,8 +2102,8 @@ class SQLFORM(FORM):
else: else:
fields = [] fields = []
columns = [] columns = []
filter1 = lambda f:isinstance(f, Field) filter1 = lambda f: isinstance(f, Field)
filter2 = lambda f:isinstance(f, Field) and f.readable filter2 = lambda f: isinstance(f, Field) and f.readable
for table in tables: for table in tables:
fields += filter(filter1, table) fields += filter(filter1, table)
columns += filter(filter2, table) columns += filter(filter2, table)
@@ -2111,15 +2117,18 @@ class SQLFORM(FORM):
if groupby is None: if groupby is None:
field_id = tables[0]._id field_id = tables[0]._id
elif groupby and isinstance(groupby, Field): elif groupby and isinstance(groupby, Field):
field_id = groupby #take the field passed as groupby #take the field passed as groupby
field_id = groupby
elif groupby and isinstance(groupby, Expression): elif groupby and isinstance(groupby, Expression):
field_id = groupby.first #take the first groupby field #take the first groupby field
while not(isinstance(field_id, Field)): # Navigate to the first Field of the expression field_id = groupby.first
while not(isinstance(field_id, Field)):
# Navigate to the first Field of the expression
field_id = field_id.first field_id = field_id.first
table = field_id.table table = field_id.table
tablename = table._tablename tablename = table._tablename
if not any(str(f) == str(field_id) for f in fields): if not any(str(f) == str(field_id) for f in fields):
fields = [f for f in fields]+[field_id] fields = [f for f in fields] + [field_id]
if upload == '<default>': if upload == '<default>':
upload = lambda filename: url(args=['download', filename]) upload = lambda filename: url(args=['download', filename])
if request.args(-2) == 'download': if request.args(-2) == 'download':
@@ -2283,8 +2292,10 @@ class SQLFORM(FORM):
expcolumns = [str(f) for f in columns] expcolumns = [str(f) for f in columns]
selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)] selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)]
if export_type.endswith('with_hidden_cols'): if export_type.endswith('with_hidden_cols'):
#expcolumns = [] start with the visible columns, which includes visible virtual fields # expcolumns = [] start with the visible columns, which
selectable_columns = [] #like expcolumns but excluding virtual # includes visible virtual fields
selectable_columns = []
#like expcolumns but excluding virtual
for table in tables: for table in tables:
for field in table: for field in table:
if field.readable and field.tablename in tablenames: if field.readable and field.tablename in tablenames:
@@ -2292,19 +2303,21 @@ class SQLFORM(FORM):
expcolumns.append(str(field)) expcolumns.append(str(field))
if not(isinstance(field, Field.Virtual)): if not(isinstance(field, Field.Virtual)):
selectable_columns.append(str(field)) selectable_columns.append(str(field))
#look for virtual fields not displayed (and virtual method fields to be added here?) #look for virtual fields not displayed (and virtual method
#fields to be added here?)
for (field_name, field) in table.iteritems(): for (field_name, field) in table.iteritems():
if isinstance(field, Field.Virtual) and not str(field) in expcolumns: if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
expcolumns.append(str(field)) expcolumns.append(str(field))
if export_type in exportManager and exportManager[export_type]: if export_type in exportManager and exportManager[export_type]:
if request.vars.keywords: if keywords:
try: try:
#the query should be constructed using searchable fields but not virtual fields #the query should be constructed using searchable
#fields but not virtual fields
sfields = reduce(lambda a, b: a + b, sfields = reduce(lambda a, b: a + b,
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables]) [[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
dbset = dbset(SQLFORM.build_query( dbset = dbset(SQLFORM.build_query(
sfields, request.vars.get('keywords', ''))) sfields, keywords))
rows = dbset.select(left=left, orderby=orderby, rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *selectable_columns) cacheable=True, *selectable_columns)
except Exception, e: except Exception, e:
@@ -2316,7 +2329,8 @@ class SQLFORM(FORM):
value = exportManager[export_type] value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value clazz = value[0] if hasattr(value, '__getitem__') else value
rows.colnames = expcolumns # expcolumns is all cols to be exported including virtual fields # expcolumns is all cols to be exported including virtual fields
rows.colnames = expcolumns
oExp = clazz(rows) oExp = clazz(rows)
filename = '.'.join(('rows', oExp.file_ext)) filename = '.'.join(('rows', oExp.file_ext))
response.headers['Content-Type'] = oExp.content_type response.headers['Content-Type'] = oExp.content_type
@@ -2330,8 +2344,7 @@ class SQLFORM(FORM):
elif not request.vars.records: elif not request.vars.records:
request.vars.records = [] request.vars.records = []
session['_web2py_grid_referrer_' + formname] = \ session['_web2py_grid_referrer_' + formname] = url2(vars=request.get_vars)
url2(vars=request.get_vars)
console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui) console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui)
error = None error = None
if create: if create:
@@ -2357,7 +2370,7 @@ class SQLFORM(FORM):
sfields_id = '%s_query_panel' % prefix sfields_id = '%s_query_panel' % prefix
skeywords_id = '%s_keywords' % prefix skeywords_id = '%s_keywords' % prefix
search_widget = lambda sfield, url: CAT(FORM( search_widget = lambda sfield, url: CAT(FORM(
INPUT(_name='keywords', _value=request.vars.keywords, INPUT(_name='keywords', _value=keywords,
_id=skeywords_id,_class='form-control', _id=skeywords_id,_class='form-control',
_onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else '' _onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else ''
), ),
@@ -2368,7 +2381,6 @@ class SQLFORM(FORM):
form = search_widget and search_widget(sfields, url()) or '' form = search_widget and search_widget(sfields, url()) or ''
console.append(add) console.append(add)
console.append(form) console.append(form)
keywords = request.vars.get('keywords', '')
try: try:
if callable(searchable): if callable(searchable):
subquery = searchable(sfields, keywords) subquery = searchable(sfields, keywords)
@@ -2428,7 +2440,7 @@ class SQLFORM(FORM):
elif key == ordermatch[1:]: elif key == ordermatch[1:]:
marker = sorter_icons[1] marker = sorter_icons[1]
header = A(header, marker, _href=url(vars=dict( header = A(header, marker, _href=url(vars=dict(
keywords=request.vars.keywords or '', keywords=keywords,
order=key)), cid=request.cid) order=key)), cid=request.cid)
headcols.append(TH(header, _class=ui.get('default'))) headcols.append(TH(header, _class=ui.get('default')))
@@ -2466,17 +2478,22 @@ class SQLFORM(FORM):
if paginate and dbset._db._adapter.dbengine == 'google:datastore': if paginate and dbset._db._adapter.dbengine == 'google:datastore':
cursor = request.vars.cursor or True cursor = request.vars.cursor or True
limitby = (0, paginate) limitby = (0, paginate)
try: page = int(request.vars.page or 1)-1 try:
except ValueError: page = 0 page = int(request.vars.page or 1) - 1
elif paginate and paginate<nrows: except ValueError:
try: page = int(request.vars.page or 1)-1 page = 0
except ValueError: page = 0 elif paginate and paginate < nrows:
limitby = (paginate*page, paginate*(page+1)) try:
page = int(request.vars.page or 1) - 1
except ValueError:
page = 0
limitby = (paginate * page, paginate * (page + 1))
else: else:
limitby = None limitby = None
try: try:
table_fields = [field for field in fields table_fields = [field for field in fields
if (field.tablename in tablenames and not(isinstance(field, Field.Virtual)))] if (field.tablename in tablenames and
not(isinstance(field, Field.Virtual)))]
if dbset._db._adapter.dbengine == 'google:datastore': if dbset._db._adapter.dbengine == 'google:datastore':
rows = dbset.select(left=left, orderby=orderby, rows = dbset.select(left=left, orderby=orderby,
groupby=groupby, limitby=limitby, groupby=groupby, limitby=limitby,
@@ -2514,8 +2531,15 @@ class SQLFORM(FORM):
paginator.append(LI('page %s' % (page+1))) paginator.append(LI('page %s' % (page+1)))
if next_cursor: if next_cursor:
d = dict(page=page+2, cursor=next_cursor) d = dict(page=page+2, cursor=next_cursor)
if order: d['order'] = order if order:
if request.vars.keywords: d['keywords'] = request.vars.keywords d['order'] = order
# see issue 1980, also at the top of the definition
# if keyworkds is in request.vars, we don't need to
# copy over the keywords parameter in the links for pagination
if 'keywords' in request.vars and not keywords:
d['keywords'] = ''
elif keywords:
d['keywords'] = keywords
paginator.append(LI( paginator.append(LI(
A('next', _href=url(vars=d), cid=request.cid))) A('next', _href=url(vars=d), cid=request.cid)))
elif paginate and paginate < nrows: elif paginate and paginate < nrows:
@@ -2531,8 +2555,13 @@ class SQLFORM(FORM):
d = dict(page=p + 1) d = dict(page=p + 1)
if order: if order:
d['order'] = order d['order'] = order
if request.vars.keywords: # see issue 1980, also at the top of the definition
d['keywords'] = request.vars.keywords # if keyworkds is in request.vars, we don't need to
# copy over the keywords parameter in the links for pagination
if 'keywords' in request.vars and not keywords:
d['keywords'] = ''
elif keywords:
d['keywords'] = keywords
return A(name, _href=url(vars=d), cid=request.cid) return A(name, _href=url(vars=d), cid=request.cid)
NPAGES = 5 # window is 2*NPAGES NPAGES = 5 # window is 2*NPAGES
if page > NPAGES + 1: if page > NPAGES + 1:
@@ -2660,7 +2689,8 @@ class SQLFORM(FORM):
_style='width:100%;overflow-x:auto;-ms-overflow-x:scroll') _style='width:100%;overflow-x:auto;-ms-overflow-x:scroll')
if selectable: if selectable:
if not callable(selectable): if not callable(selectable):
#now expect that selectable and related parameters are iterator (list, tuple, etc) #now expect that selectable and related parameters are
#iterator (list, tuple, etc)
inputs = [] inputs = []
for i, submit_info in enumerate(selectable): for i, submit_info in enumerate(selectable):
submit_text = submit_info[0] submit_text = submit_info[0]
@@ -2708,7 +2738,7 @@ class SQLFORM(FORM):
link = url2(vars=dict( link = url2(vars=dict(
order=request.vars.order or '', order=request.vars.order or '',
_export_type=k, _export_type=k,
keywords=request.vars.keywords or '')) keywords=keywords or ''))
export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default')) export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default'))
export_menu = \ export_menu = \
DIV(T('Export:'), _class="w2p_export_menu", *export_links) DIV(T('Export:'), _class="w2p_export_menu", *export_links)
@@ -2797,7 +2827,7 @@ class SQLFORM(FORM):
elif callable(table._format): elif callable(table._format):
return table._format(row) return table._format(row)
else: else:
return '#'+str(row.id) return '#' + str(row.id)
try: try:
nargs = len(args) + 1 nargs = len(args) + 1
previous_tablename, previous_fieldname, previous_id = \ previous_tablename, previous_fieldname, previous_id = \
@@ -2876,7 +2906,7 @@ class SQLFORM(FORM):
opts = [OPTION(T('References')+':', _value='')] opts = [OPTION(T('References')+':', _value='')]
linked = [] linked = []
if linked_tables: if linked_tables:
for item in linked_tables: for item in linked_tables:
tb = None tb = None
if isinstance(item, Table) and item._tablename in check: if isinstance(item, Table) and item._tablename in check:
tablename = item._tablename tablename = item._tablename
@@ -3001,12 +3031,16 @@ class SQLTABLE(TABLE):
return return
REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD REGEX_TABLE_DOT_FIELD = sqlrows.db._adapter.REGEX_TABLE_DOT_FIELD
if not columns: if not columns:
columns = [c for c in sqlrows.colnames if REGEX_TABLE_DOT_FIELD.match(c)] columns = list(sqlrows.colnames)
if headers == 'fieldname:capitalize': if headers == 'fieldname:capitalize':
headers = {} headers = {}
for c in columns: for c in columns:
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups() tfmatch=REGEX_TABLE_DOT_FIELD.match(c)
headers[t + '.' + f] = f.replace('_', ' ').title() if tfmatch:
(t, f) = REGEX_TABLE_DOT_FIELD.match(c).groups()
headers[t + '.' + f] = f.replace('_', ' ').title()
else:
headers[c]=c
elif headers == 'labels': elif headers == 'labels':
headers = {} headers = {}
for c in columns: for c in columns:
@@ -3025,7 +3059,7 @@ class SQLTABLE(TABLE):
headers = {} headers = {}
else: else:
for c in columns: # new implement dict for c in columns: # new implement dict
c = '.'.join(REGEX_TABLE_DOT_FIELD.match(c).groups()) c = str(c)
if isinstance(headers.get(c, c), dict): if isinstance(headers.get(c, c), dict):
coldict = headers.get(c, c) coldict = headers.get(c, c)
attrcol = dict() attrcol = dict()
+46
View File
@@ -44,6 +44,9 @@ ALLOWED_DATATYPES = [
'json', 'json',
] ]
IS_POSTGRESQL = 'postgres' in DEFAULT_URI
def setUpModule(): def setUpModule():
pass pass
@@ -404,6 +407,13 @@ class TestLike(unittest.TestCase):
self.assertEqual(db(db.tt.aa.upper().like('A%')).count(), 1) self.assertEqual(db(db.tt.aa.upper().like('A%')).count(), 1)
self.assertEqual(db(db.tt.aa.upper().like('%B%')).count(),1) self.assertEqual(db(db.tt.aa.upper().like('%B%')).count(),1)
self.assertEqual(db(db.tt.aa.upper().like('%C')).count(), 1) self.assertEqual(db(db.tt.aa.upper().like('%C')).count(), 1)
# startswith endswith tests
self.assertEqual(db(db.tt.aa.startswith('a')).count(), 1)
self.assertEqual(db(db.tt.aa.endswith('c')).count(), 1)
self.assertEqual(db(db.tt.aa.startswith('c')).count(), 0)
self.assertEqual(db(db.tt.aa.endswith('a')).count(), 0)
db.tt.drop() db.tt.drop()
db.define_table('tt', Field('aa', 'integer')) db.define_table('tt', Field('aa', 'integer'))
self.assertEqual(db.tt.insert(aa=1111111111), 1) self.assertEqual(db.tt.insert(aa=1111111111), 1)
@@ -1528,6 +1538,42 @@ class TestQuotesByDefault(unittest.TestCase):
def testme(self): def testme(self):
return return
class TestGis(unittest.TestCase):
def testGeometry(self):
from gluon.dal import geoPoint, geoLine, geoPolygon
if not IS_POSTGRESQL: return
db = DAL(DEFAULT_URI, check_reserved=['all'], ignore_field_case=False)
t0 = db.define_table('t0', Field('point', 'geometry()'))
t1 = db.define_table('t1', Field('line', 'geometry(public, 4326, 2)'))
t2 = db.define_table('t2', Field('polygon', 'geometry(public, 4326, 2)'))
t0.insert(point=geoPoint(1,1))
text = db(db.t0.id).select(db.t0.point.st_astext()).first()[db.t0.point.st_astext()]
self.assertEqual(text, "POINT(1 1)")
t1.insert(line=geoLine((1,1),(2,2)))
text = db(db.t1.id).select(db.t1.line.st_astext()).first()[db.t1.line.st_astext()]
self.assertEqual(text, "LINESTRING(1 1,2 2)")
t2.insert(polygon=geoPolygon((0,0),(2,0),(2,2),(0,2),(0,0)))
text = db(db.t2.id).select(db.t2.polygon.st_astext()).first()[db.t2.polygon.st_astext()]
self.assertEqual(text, "POLYGON((0 0,2 0,2 2,0 2,0 0))")
query = t0.point.st_intersects(geoLine((0,0),(2,2)))
output = db(query).select(db.t0.point).first()[db.t0.point]
self.assertEqual(output, "POINT(1 1)")
query = t2.polygon.st_contains(geoPoint(1,1))
n = db(query).count()
self.assertEqual(n, 1)
x=t0.point.st_x()
y=t0.point.st_y()
point = db(t0.id).select(x, y).first()
self.assertEqual(point[x], 1)
self.assertEqual(point[y], 1)
t0.drop()
t1.drop()
t2.drop()
return
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
tearDownModule() tearDownModule()
+4
View File
@@ -35,6 +35,10 @@ try:
#due to http://bugs.python.org/issue10845, testing multiprocessing in python is impossible #due to http://bugs.python.org/issue10845, testing multiprocessing in python is impossible
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
MP_WORKING = 0 MP_WORKING = 0
#multiprocessing is also not available on GAE. Since tests randomly
#fail, let's not make them on it too
if 'datastore' in os.getenv('DB', ''):
MP_WORKING = 0
except ImportError: except ImportError:
pass pass
+2 -4
View File
@@ -3011,15 +3011,13 @@ class Auth(object):
if self.settings.prevent_password_reset_attacks: if self.settings.prevent_password_reset_attacks:
key = request.vars.key key = request.vars.key
if not key and len(request.args)>1:
key = request.args[-1]
if key: if key:
session._reset_password_key = key session._reset_password_key = key
redirect(self.url(args='reset_password')) redirect(self.url(args='reset_password'))
else: else:
key = session._reset_password_key key = session._reset_password_key
else: else:
key = request.vars.key or getarg(-1) key = request.vars.key
try: try:
t0 = int(key.split('-')[0]) t0 = int(key.split('-')[0])
if time.time() - t0 > 60 * 60 * 24: if time.time() - t0 > 60 * 60 * 24:
@@ -3138,7 +3136,7 @@ class Auth(object):
def email_reset_password(self, user): def email_reset_password(self, user):
reset_password_key = str(int(time.time())) + '-' + web2py_uuid() reset_password_key = str(int(time.time())) + '-' + web2py_uuid()
link = self.url(self.settings.function, link = self.url(self.settings.function,
args=('reset_password', reset_password_key), args=('reset_password',), vars={'key': reset_password_key},
scheme=True) scheme=True)
d = dict(user) d = dict(user)
d.update(dict(key=reset_password_key, link=link)) d.update(dict(key=reset_password_key, link=link))
+3 -3
View File
@@ -5,10 +5,10 @@ import sys
import os import os
import optparse import optparse
if '__file__' in globals(): if hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(__file__))
elif hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(sys.executable)) path = os.path.dirname(os.path.abspath(sys.executable))
elif '__file__' in globals():
path = os.path.dirname(os.path.abspath(__file__))
else: else:
path = os.getcwd() path = os.getcwd()
os.chdir(path) os.chdir(path)
-1
View File
@@ -32,7 +32,6 @@ from __future__ import with_statement
import sys import sys
import os import os
print os.path.join(*__file__.split(os.sep)[:-2] or ['.'])
sys.path.append(os.path.join(*__file__.split(os.sep)[:-2] or ['.'])) sys.path.append(os.path.join(*__file__.split(os.sep)[:-2] or ['.']))
from gluon import current from gluon import current
+302
View File
@@ -0,0 +1,302 @@
echo "This script will:
1) Install modules needed to run web2py on Fedora and CentOS/RHEL
2) Install Python 2.6 to /opt and recompile wsgi if not provided
2) Install web2py in /opt/web-apps/
3) Configure SELinux and iptables
5) Create a self signed ssl certificate
6) Setup web2py with mod_wsgi
7) Create virtualhost entries so that web2py responds for '/'
8) Restart Apache.
You should probably read this script before running it.
Although SELinux permissions changes have been made,
further SELinux changes will be required for your personal
apps. (There may also be additional changes required for the
bundled apps.) As a last resort, SELinux can be disabled.
A simple iptables configuration has been applied. You may
want to review it to verify that it meets your needs.
Finally, if you require a proxy to access the Internet, please
set up your machine to do so before running this script.
(author: berubejd)
Press ENTER to continue...[ctrl+C to abort]"
read CONFIRM
#!/bin/bash
# (modified for centos7: Dragan (spamperakojotgenije@gmail.com)
###
### Phase 0 - This may get messy. Lets work from a temporary directory
###
current_dir=`pwd`
if [ -d /tmp/setup-web2py/ ]; then
mv /tmp/setup-web2py/ /tmp/setup-web2py.old/
fi
mkdir -p /tmp/setup-web2py
cd /tmp/setup-web2py
###
### Phase 1 - Requirements installation
###
echo
echo " - Installing packages"
echo
# Verify packages are up to date
yum update
# Install required packages
yum install httpd mod_ssl mod_wsgi wget python
###
### Phase 2 - Install web2py
###
echo
echo " - Downloading, installing, and starting web2py"
echo
# Create web-apps directory, if required
if [ ! -d "/opt/web-apps" ]; then
mkdir -p /opt/web-apps
chmod 755 /opt
chmod 755 /opt/web-apps
fi
cd /opt/web-apps
# Download web2py
if [ -e web2py_src.zip* ]; then
rm web2py_src.zip*
fi
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
chown -R apache:apache web2py
###
### Phase 3 - Setup SELinux context
###
### SELinux doesn't behave well with web2py, for details
### see https://groups.google.com/forum/?fromgroups#!searchin/web2py/selinux/web2py/_thPGA9YhK4/dSnvF3D_lswJ
###
### For now you'll have to disable SELinux
# Allow http_tmp_exec required for wsgi
RETV=`setsebool -P httpd_tmp_exec on > /dev/null 2>&1; echo $?`
if [ ! ${RETV} -eq 0 ]; then
# CentOS doesn't support httpd_tmp_exec
cd /tmp/setup-web2py
# Create the SELinux policy
cat > httpd.te <<EOF
module httpd 1.0;
require {
type httpd_t;
class process execmem;
}
#============= httpd_t ==============
allow httpd_t self:process execmem;
EOF
checkmodule -M -m -o httpd.mod httpd.te
semodule_package -o httpd.pp -m httpd.mod
semodule -i httpd.pp
fi
# Setup the overall web2py SELinux context
cd /opt
chcon -R -t httpd_user_content_t web-apps/
cd /opt/web-apps/web2py/applications
# Setup the proper context on the writable application directories
for app in `ls`
do
for dir in databases cache errors sessions private uploads
do
mkdir ${app}/${dir}
chown apache:apache ${app}/${dir}
chcon -R -t tmp_t ${app}/${dir}
done
done
###
### Phase 4 - Configure iptables
###
cd /tmp/setup-web2py
# Create rules file - based upon
# http://articles.slicehost.com/assets/2007/9/4/iptables.txt
# centos7 uses firewalld
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --zone=public --add-port=22/tcp --permanent
firewall-cmd --reload
###
### Phase 5 - Setup SSL
###
echo
echo " - Creating a self signed certificate"
echo
# Verify ssl directory exists
if [ ! -d "/etc/httpd/ssl" ]; then
mkdir -p /etc/httpd/ssl
fi
# Generate and protect certificate
openssl genrsa 1024 > /etc/httpd/ssl/self_signed.key
openssl req -new -x509 -nodes -sha1 -days 365 -key /etc/httpd/ssl/self_signed.key > /etc/httpd/ssl/self_signed.cert
openssl x509 -noout -fingerprint -text < /etc/httpd/ssl/self_signed.cert > /etc/httpd/ssl/self_signed.info
chmod 400 /etc/httpd/ssl/self_signed.*
###
### Phase 6 - Configure Apache
###
echo
echo " - Configure Apache to use mod_wsgi"
echo
# Create config
if [ -e /etc/httpd/conf.d/welcome.conf ]; then
mv /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.d/welcome.conf.disabled
fi
cat > /etc/httpd/conf.d/default.conf <<EOF
NameVirtualHost *:80
NameVirtualHost *:443
<VirtualHost *:80>
WSGIDaemonProcess web2py user=apache group=apache processes=1 threads=1
WSGIProcessGroup web2py
WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py
WSGIPassAuthorization On
<Directory /opt/web-apps/web2py>
AllowOverride None
Order Allow,Deny
Deny from all
<Files wsgihandler.py>
Require all granted
Allow from all
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
<Directory /opt/web-apps/web2py/applications/*/static>
Options -Indexes
Order Allow,Deny
Allow from all
Require all granted
</Directory>
<Location /admin>
Deny from all
</Location>
<LocationMatch ^/([^/]+)/appadmin>
Deny from all
</LocationMatch>
CustomLog /var/log/httpd/access_log common
ErrorLog /var/log/httpd/error_log
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/self_signed.cert
SSLCertificateKeyFile /etc/httpd/ssl/self_signed.key
WSGIProcessGroup web2py
WSGIScriptAlias / /opt/web-apps/web2py/wsgihandler.py
WSGIPassAuthorization On
<Directory /opt/web-apps/web2py>
AllowOverride None
Order Allow,Deny
Deny from all
<Files wsgihandler.py>
Require all granted
Allow from all
</Files>
</Directory>
AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*) /opt/web-apps/web2py/applications/\$1/static/\$2
<Directory /opt/web-apps/web2py/applications/*/static>
Options -Indexes
ExpiresActive On
ExpiresDefault "access plus 1 hour"
Order Allow,Deny
Allow from all
Require all granted
</Directory>
CustomLog /var/log/httpd/access_log common
ErrorLog /var/log/httpd/error_log
</VirtualHost>
EOF
# Fix wsgi socket locations
echo "WSGISocketPrefix run/wsgi" >> /etc/httpd/conf.d/wsgi.conf
# Restart Apache to pick up changes
systemctl restart httpd.service
###
### Phase 7 - Setup web2py admin password
###
echo
echo " - Setup web2py admin password"
echo
cd /opt/web-apps/web2py
sudo -u apache python -c "from gluon.main import save_password; save_password(raw_input('admin password: '),443)"
###
### Phase 8 - Verify that required services start at boot
###
/sbin/chkconfig iptables on
/sbin/chkconfig httpd on
###
### Phase 999 - Done!
###
# Change back to original directory
cd ${current_directory}
echo " - Complete!"
echo
+3 -3
View File
@@ -4,10 +4,10 @@
import os import os
import sys import sys
if '__file__' in globals(): if hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(__file__))
elif hasattr(sys, 'frozen'):
path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe path = os.path.dirname(os.path.abspath(sys.executable)) # for py2exe
elif '__file__' in globals():
path = os.path.dirname(os.path.abspath(__file__))
else: # should never happen else: # should never happen
path = os.getcwd() path = os.getcwd()
os.chdir(path) os.chdir(path)